1144 lines
42 KiB
Lua
1144 lines
42 KiB
Lua
mg_villages.VILLAGE_CHECK_RADIUS = 2
|
|
mg_villages.VILLAGE_CHECK_COUNT = 1
|
|
--mg_villages.VILLAGE_CHANCE = 28
|
|
--mg_villages.VILLAGE_MIN_SIZE = 20
|
|
--mg_villages.VILLAGE_MAX_SIZE = 40
|
|
mg_villages.VILLAGE_CHANCE = 28
|
|
mg_villages.VILLAGE_MIN_SIZE = 25
|
|
mg_villages.VILLAGE_MAX_SIZE = 90 --55
|
|
mg_villages.FIRST_ROADSIZE = 3
|
|
mg_villages.BIG_ROAD_CHANCE = 0
|
|
|
|
-- Enable that for really big villages (there are also really slow to generate)
|
|
--[[mg_villages.VILLAGE_CHECK_RADIUS = 3
|
|
mg_villages.VILLAGE_CHECK_COUNT = 3
|
|
mg_villages.VILLAGE_CHANCE = 28
|
|
mg_villages.VILLAGE_MIN_SIZE = 100
|
|
mg_villages.VILLAGE_MAX_SIZE = 150
|
|
mg_villages.FIRST_ROADSIZE = 5
|
|
mg_villages.BIG_ROAD_CHANCE = 50]]
|
|
|
|
|
|
-- if set to false, villages will not be integrated into the terrain - which looks very bad
|
|
mg_villages.ENABLE_TERRAIN_BLEND = true;
|
|
-- if set to false, holes digged by cavegen and mudflow inside the village will not be repaired; houses will be destroyed
|
|
mg_villages.UNDO_CAVEGEN_AND_MUDFLOW = true;
|
|
|
|
-- on average, every n.th node may be one of these trees - and it will be a relatively dense packed forrest
|
|
mg_villages.sapling_probability = {};
|
|
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'default:sapling' ) ] = 25; -- suitable for a relatively dense forrest of normal trees
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'default:junglesapling' ) ] = 40; -- jungletrees are a bit bigger and need more space
|
|
if( minetest.get_modpath( 'mg' )) then
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'mg:savannasapling' ) ] = 30;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'mg:pinesapling' ) ] = 35;
|
|
end
|
|
if( minetest.get_modpath( 'moretrees' )) then
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:birch_sapling_ongen' ) ] = 200;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:spruce_sapling_ongen' ) ] = 200;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:fir_sapling_ongen' ) ] = 90;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:jungletree_sapling_ongen' ) ] = 200;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:beech_sapling_ongen' ) ] = 30;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:apple_sapling_ongen' ) ] = 380;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:oak_sapling_ongen' ) ] = 380; -- ca 20x20; height: 10
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:sequoia_sapling_ongen' ) ] = 90; -- ca 10x10
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:palm_sapling_ongen' ) ] = 90;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:pine_sapling_ongen' ) ] = 200;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:willow_sapling_ongen' ) ] = 380;
|
|
mg_villages.sapling_probability[ minetest.get_content_id( 'moretrees:rubber_tree_sapling_ongen' ) ] = 380;
|
|
end
|
|
|
|
|
|
|
|
local function is_village_block(minp)
|
|
local x, z = math.floor(minp.x/80), math.floor(minp.z/80)
|
|
local vcc = mg_villages.VILLAGE_CHECK_COUNT
|
|
return (x%vcc == 0) and (z%vcc == 0)
|
|
end
|
|
|
|
mg_villages.villages_at_point = function(minp, noise1)
|
|
if not is_village_block(minp) then return {} end
|
|
local vcr, vcc = mg_villages.VILLAGE_CHECK_RADIUS, mg_villages.VILLAGE_CHECK_COUNT
|
|
-- Check if there's another village nearby
|
|
for xi = -vcr, vcr, vcc do
|
|
for zi = -vcr, 0, vcc do
|
|
if xi ~= 0 or zi ~= 0 then
|
|
local mp = {x = minp.x + 80*xi, z = minp.z + 80*zi}
|
|
local pi = PseudoRandom(mg_villages.get_bseed(mp))
|
|
local s = pi:next(1, 400)
|
|
local x = pi:next(mp.x, mp.x + 79)
|
|
local z = pi:next(mp.z, mp.z + 79)
|
|
if s <= mg_villages.VILLAGE_CHANCE and noise1:get2d({x = x, y = z}) >= -0.3 then return {} end
|
|
end
|
|
end
|
|
end
|
|
local pr = PseudoRandom(mg_villages.get_bseed(minp))
|
|
if pr:next(1, 400) > mg_villages.VILLAGE_CHANCE then return {} end -- No village here
|
|
local x = pr:next(minp.x, minp.x + 79)
|
|
local z = pr:next(minp.z, minp.z + 79)
|
|
if noise1:get2d({x = x, y = z}) < -0.3 then return {} end -- Deep in the ocean
|
|
|
|
-- fallback: type "nore" (that is what the mod originally came with)
|
|
local village_type = 'nore';
|
|
-- if this is the first village for this world, take a medieval one
|
|
if( (not( mg_villages.all_villages ) or mg_villages.anz_villages < 1) and minetest.get_modpath("cottages") ) then
|
|
village_type = 'medieval';
|
|
else
|
|
village_type = mg_villages.village_types[ pr:next(1, #mg_villages.village_types )]; -- select a random type
|
|
end
|
|
|
|
if( not( mg_villages.village_sizes[ village_type ] )) then
|
|
mg_villages.village_sizes[ village_type ] = { min = mg_villages.VILLAGE_MIN_SIZE, max = mg_villages.VILLAGE_MAX_SIZE };
|
|
end
|
|
local size = pr:next(mg_villages.village_sizes[ village_type ].min, mg_villages.village_sizes[ village_type ].max)
|
|
-- local height = pr:next(5, 20)
|
|
local height = pr:next(1, 5)
|
|
-- villages of a size >= 40 are always placed at a height of 1
|
|
if( size >= 40 ) then
|
|
height = 1;
|
|
-- slightly smaller but still relatively large villages have a deterministic height now as well
|
|
elseif( size >= 30 ) then
|
|
height = 40-height;
|
|
elseif( size >= 25 ) then
|
|
height = 35-height;
|
|
-- even smaller villages need to have a height depending on their sourroundings (at least they're pretty small!)
|
|
end
|
|
|
|
-- print("A village of type \'"..tostring( village_type ).."\' of size "..tostring( size ).." spawned at: x = "..x..", z = "..z)
|
|
--print("A village spawned at: x = "..x..", z = "..z)
|
|
return {{vx = x, vz = z, vs = size, vh = height, village_type = village_type}}
|
|
end
|
|
|
|
--local function dist_center2(ax, bsizex, az, bsizez)
|
|
-- return math.max((ax+bsizex)*(ax+bsizex),ax*ax)+math.max((az+bsizez)*(az+bsizez),az*az)
|
|
--end
|
|
|
|
local function inside_village2(bx, sx, bz, sz, village, vnoise)
|
|
return mg_villages.inside_village(bx, bz, village, vnoise) and mg_villages.inside_village(bx+sx, bz, village, vnoise) and mg_villages.inside_village(bx, bz+sz, village, vnoise) and mg_villages.inside_village(bx+sx, bz+sz, village, vnoise)
|
|
end
|
|
|
|
local function choose_building(l, pr, village_type)
|
|
--::choose::
|
|
local btype
|
|
while true do
|
|
local p = pr:next(1, 3000)
|
|
for b, i in ipairs(mg_villages.BUILDINGS) do
|
|
if i.weight[ village_type ] and i.weight[ village_type ] > 0 and i.max_weight and i.max_weight[ village_type ] and i.max_weight[ village_type ] >= p then
|
|
btype = b
|
|
break
|
|
end
|
|
end
|
|
-- in case no building was found: take the last one that fits
|
|
if( not( btype )) then
|
|
for i=#mg_villages.BUILDINGS,1,-1 do
|
|
if( mg_villages.BUILDINGS[i].weight and mg_villages.BUILDINGS[i].weight[ village_type ] and mg_villages.BUILDINGS[i].weight[ village_type ] > 0 ) then
|
|
btype = i;
|
|
i = 1;
|
|
end
|
|
end
|
|
end
|
|
if( not( btype )) then
|
|
return 1;
|
|
end
|
|
if( #l<1
|
|
or not( mg_villages.BUILDINGS[btype].avoid )
|
|
or mg_villages.BUILDINGS[btype].avoid==''
|
|
or not( mg_villages.BUILDINGS[ l[#l].btype ].avoid )
|
|
or mg_villages.BUILDINGS[btype].avoid ~= mg_villages.BUILDINGS[ l[#l].btype ].avoid) then
|
|
|
|
if mg_villages.BUILDINGS[btype].pervillage ~= nil then
|
|
local n = 0
|
|
for j=1, #l do
|
|
if( l[j].btype == btype or (mg_villages.BUILDINGS[btype].typ and mg_villages.BUILDINGS[btype].typ == mg_villages.BUILDINGS[ l[j].btype ].typ)) then
|
|
n = n + 1
|
|
end
|
|
end
|
|
--if n >= mg_villages.BUILDINGS[btype].pervillage then
|
|
-- goto choose
|
|
--end
|
|
if n < mg_villages.BUILDINGS[btype].pervillage then
|
|
return btype
|
|
end
|
|
else
|
|
return btype
|
|
end
|
|
end
|
|
end
|
|
--return btype
|
|
end
|
|
|
|
local function choose_building_rot(l, pr, orient, village_type)
|
|
local btype = choose_building(l, pr, village_type)
|
|
local rotation
|
|
if mg_villages.BUILDINGS[btype].no_rotate then
|
|
rotation = 0
|
|
else
|
|
if mg_villages.BUILDINGS[btype].orients == nil then
|
|
mg_villages.BUILDINGS[btype].orients = {0,1,2,3}
|
|
end
|
|
rotation = (orient+mg_villages.BUILDINGS[btype].orients[pr:next(1, #mg_villages.BUILDINGS[btype].orients)])%4
|
|
end
|
|
local bsizex = mg_villages.BUILDINGS[btype].sizex
|
|
local bsizez = mg_villages.BUILDINGS[btype].sizez
|
|
if rotation%2 == 1 then
|
|
bsizex, bsizez = bsizez, bsizex
|
|
end
|
|
return btype, rotation, bsizex, bsizez
|
|
end
|
|
|
|
local function placeable(bx, bz, bsizex, bsizez, l, exclude_roads, orientation)
|
|
for _, a in ipairs(l) do
|
|
-- with < instead of <=, space_between_buildings can be zero (important for towns where houses are closely packed)
|
|
if (a.btype ~= "road" or not exclude_roads) and math.abs(bx+bsizex/2-a.x-a.bsizex/2)<(bsizex+a.bsizex)/2 and math.abs(bz+bsizez/2-a.z-a.bsizez/2)<(bsizez+a.bsizez)/2 then
|
|
-- dirt roads which go at a 90 degree angel to the current road are not a problem
|
|
if( not( orientation ) or a.o%2 == orientation%2 ) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function road_in_building(rx, rz, rdx, rdz, roadsize, l)
|
|
if rdx == 0 then
|
|
return not placeable(rx-roadsize+1, rz, 2*roadsize-2, 0, l, true)
|
|
else
|
|
return not placeable(rx, rz-roadsize+1, 0, 2*roadsize-2, l, true)
|
|
end
|
|
end
|
|
|
|
local function when(a, b, c)
|
|
if a then return b else return c end
|
|
end
|
|
|
|
mg_villages.road_nr = 0;
|
|
|
|
local function generate_road(village, l, pr, roadsize, rx, rz, rdx, rdz, vnoise, space_between_buildings)
|
|
local vx, vz, vh, vs = village.vx, village.vz, village.vh, village.vs
|
|
local village_type = village.village_type;
|
|
local calls_to_do = {}
|
|
local rxx = rx
|
|
local rzz = rz
|
|
local mx, m2x, mz, m2z, mmx, mmz
|
|
mx, m2x, mz, m2z = rx, rx, rz, rz
|
|
local orient1, orient2
|
|
if rdx == 0 then
|
|
orient1 = 0
|
|
orient2 = 2
|
|
else
|
|
orient1 = 3
|
|
orient2 = 1
|
|
end
|
|
-- we have one more road
|
|
mg_villages.road_nr = mg_villages.road_nr + 1;
|
|
while mg_villages.inside_village(rx, rz, village, vnoise) and not road_in_building(rx, rz, rdx, rdz, roadsize, l) do
|
|
if roadsize > 1 and pr:next(1, 4) == 1 then
|
|
--generate_road(vx, vz, vs, vh, l, pr, roadsize-1, rx, rz, math.abs(rdz), math.abs(rdx))
|
|
calls_to_do[#calls_to_do+1] = {rx=rx+(roadsize - 1)*rdx, rz=rz+(roadsize - 1)*rdz, rdx=math.abs(rdz), rdz=math.abs(rdx)}
|
|
m2x = rx + (roadsize - 1)*rdx
|
|
m2z = rz + (roadsize - 1)*rdz
|
|
rx = rx + (2*roadsize - 1)*rdx
|
|
rz = rz + (2*roadsize - 1)*rdz
|
|
end
|
|
--else
|
|
--::loop::
|
|
local exitloop = false
|
|
local bx
|
|
local bz
|
|
local tries = 0
|
|
while true do
|
|
if not mg_villages.inside_village(rx, rz, village, vnoise) or road_in_building(rx, rz, rdx, rdz, roadsize, l) then
|
|
exitloop = true
|
|
break
|
|
end
|
|
local village_type_sub = village_type;
|
|
if( mg_villages.medieval_subtype and village_type_sub == 'medieval' and math.abs(village.vx-rx)>20 and math.abs(village.vz-rz)>20) then
|
|
village_type_sub = 'fields';
|
|
end
|
|
btype, rotation, bsizex, bsizez = choose_building_rot(l, pr, orient1, village_type_sub)
|
|
bx = rx + math.abs(rdz)*(roadsize+1) - when(rdx==-1, bsizex-1, 0)
|
|
bz = rz + math.abs(rdx)*(roadsize+1) - when(rdz==-1, bsizez-1, 0)
|
|
if placeable(bx, bz, bsizex, bsizez, l) and inside_village2(bx, bsizex, bz, bsizez, village, vnoise) then
|
|
break
|
|
end
|
|
if tries > 5 then
|
|
rx = rx + rdx
|
|
rz = rz + rdz
|
|
tries = 0
|
|
else
|
|
tries = tries + 1
|
|
end
|
|
--goto loop
|
|
end
|
|
if exitloop then break end
|
|
rx = rx + (bsizex+space_between_buildings)*rdx
|
|
rz = rz + (bsizez+space_between_buildings)*rdz
|
|
mx = rx - 2*rdx
|
|
mz = rz - 2*rdz
|
|
l[#l+1] = {x=bx, y=vh, z=bz, btype=btype, bsizex=bsizex, bsizez=bsizez, brotate = rotation, road_nr = mg_villages.road_nr, side=1, o=orient1 }
|
|
--end
|
|
end
|
|
rx = rxx
|
|
rz = rzz
|
|
while mg_villages.inside_village(rx, rz, village, vnoise) and not road_in_building(rx, rz, rdx, rdz, roadsize, l) do
|
|
if roadsize > 1 and pr:next(1, 4) == 1 then
|
|
--generate_road(vx, vz, vs, vh, l, pr, roadsize-1, rx, rz, -math.abs(rdz), -math.abs(rdx))
|
|
calls_to_do[#calls_to_do+1] = {rx=rx+(roadsize - 1)*rdx, rz=rz+(roadsize - 1)*rdz, rdx=-math.abs(rdz), rdz=-math.abs(rdx)}
|
|
m2x = rx + (roadsize - 1)*rdx
|
|
m2z = rz + (roadsize - 1)*rdz
|
|
rx = rx + (2*roadsize - 1)*rdx
|
|
rz = rz + (2*roadsize - 1)*rdz
|
|
end
|
|
--else
|
|
--::loop::
|
|
local exitloop = false
|
|
local bx
|
|
local bz
|
|
local tries = 0
|
|
while true do
|
|
if not mg_villages.inside_village(rx, rz, village, vnoise) or road_in_building(rx, rz, rdx, rdz, roadsize, l) then
|
|
exitloop = true
|
|
break
|
|
end
|
|
local village_type_sub = village_type;
|
|
if( mg_villages.medieval_subtype and village_type_sub == 'medieval' and math.abs(village.vx-rx)>(village.vs/3) and math.abs(village.vz-rz)>(village.vs/3)) then
|
|
village_type_sub = 'fields';
|
|
end
|
|
btype, rotation, bsizex, bsizez = choose_building_rot(l, pr, orient2, village_type_sub)
|
|
bx = rx - math.abs(rdz)*(bsizex+roadsize) - when(rdx==-1, bsizex-1, 0)
|
|
bz = rz - math.abs(rdx)*(bsizez+roadsize) - when(rdz==-1, bsizez-1, 0)
|
|
if placeable(bx, bz, bsizex, bsizez, l) and inside_village2(bx, bsizex, bz, bsizez, village, vnoise) then
|
|
break
|
|
end
|
|
if tries > 5 then
|
|
rx = rx + rdx
|
|
rz = rz + rdz
|
|
tries = 0
|
|
else
|
|
tries = tries + 1
|
|
end
|
|
--goto loop
|
|
end
|
|
if exitloop then break end
|
|
rx = rx + (bsizex+space_between_buildings)*rdx
|
|
rz = rz + (bsizez+space_between_buildings)*rdz
|
|
m2x = rx - 2*rdx
|
|
m2z = rz - 2*rdz
|
|
l[#l+1] = {x=bx, y=vh, z=bz, btype=btype, bsizex=bsizex, bsizez=bsizez, brotate = rotation, road_nr = mg_villages.road_nr, side=2, o=orient2}
|
|
--end
|
|
end
|
|
if road_in_building(rx, rz, rdx, rdz, roadsize, l) then
|
|
mmx = rx - 2*rdx
|
|
mmz = rz - 2*rdz
|
|
end
|
|
mx = mmx or rdx*math.max(rdx*mx, rdx*m2x)
|
|
mz = mmz or rdz*math.max(rdz*mz, rdz*m2z)
|
|
if rdx == 0 then
|
|
rxmin = rx - roadsize + 1
|
|
rxmax = rx + roadsize - 1
|
|
rzmin = math.min(rzz, mz)
|
|
rzmax = math.max(rzz, mz)
|
|
-- prolong the main road to the borders of the village
|
|
if( mg_villages.road_nr == 1 ) then
|
|
while( mg_villages.inside_village_area(rxmin, rzmin, village, vnoise)) do
|
|
rzmin = rzmin-1;
|
|
rzmax = rzmax+1;
|
|
end
|
|
rzmin = rzmin-1;
|
|
rzmax = rzmax+1;
|
|
while( mg_villages.inside_village_area(rxmax, rzmax, village, vnoise)) do
|
|
rzmax = rzmax+1;
|
|
end
|
|
rzmax = rzmax+1;
|
|
end
|
|
else
|
|
rzmin = rz - roadsize + 1
|
|
rzmax = rz + roadsize - 1
|
|
rxmin = math.min(rxx, mx)
|
|
rxmax = math.max(rxx, mx)
|
|
-- prolong the main road to the borders of the village
|
|
if( mg_villages.road_nr == 1 ) then
|
|
while( mg_villages.inside_village_area(rxmin, rzmin, village, vnoise)) do
|
|
rxmin = rxmin-1;
|
|
rxmax = rxmax+1;
|
|
end
|
|
rxmin = rxmin-1;
|
|
rxmax = rxmax+1;
|
|
while( mg_villages.inside_village_area(rxmax, rzmax, village, vnoise)) do
|
|
rxmax = rxmax+1;
|
|
end
|
|
rxmax = rxmax+1;
|
|
end
|
|
end
|
|
l[#l+1] = {x = rxmin, y = vh, z = rzmin, btype = "road",
|
|
bsizex = rxmax - rxmin + 1, bsizez = rzmax - rzmin + 1, brotate = 0, road_nr = mg_villages.road_nr}
|
|
|
|
for _, i in ipairs(calls_to_do) do
|
|
local new_roadsize = roadsize - 1
|
|
if pr:next(1, 100) <= mg_villages.BIG_ROAD_CHANCE then
|
|
new_roadsize = roadsize
|
|
end
|
|
|
|
--generate_road(vx, vz, vs, vh, l, pr, new_roadsize, i.rx, i.rz, i.rdx, i.rdz, vnoise)
|
|
calls[calls.index] = {village, l, pr, new_roadsize, i.rx, i.rz, i.rdx, i.rdz, vnoise, space_between_buildings}
|
|
calls.index = calls.index+1
|
|
end
|
|
end
|
|
|
|
local function generate_bpos(village, pr, vnoise, space_between_buildings)
|
|
local vx, vz, vh, vs = village.vx, village.vz, village.vh, village.vs
|
|
local l = {}
|
|
local rx = vx - vs
|
|
--[=[local l={}
|
|
local total_weight = 0
|
|
for _, i in ipairs(mg_villages.BUILDINGS) do
|
|
if i.weight == nil then i.weight = 1 end
|
|
total_weight = total_weight+i.weight
|
|
i.max_weight = total_weight
|
|
end
|
|
local multiplier = 3000/total_weight
|
|
for _,i in ipairs(mg_villages.BUILDINGS) do
|
|
i.max_weight = i.max_weight*multiplier
|
|
end
|
|
for i=1, 2000 do
|
|
bx = pr:next(vx-vs, vx+vs)
|
|
bz = pr:next(vz-vs, vz+vs)
|
|
::choose::
|
|
--[[btype = pr:next(1, #mg_villages.BUILDINGS)
|
|
if mg_villages.BUILDINGS[btype].chance ~= nil then
|
|
if pr:next(1, mg_villages.BUILDINGS[btype].chance) ~= 1 then
|
|
goto choose
|
|
end
|
|
end]]
|
|
p = pr:next(1, 3000)
|
|
for b, i in ipairs(mg_villages.BUILDINGS) do
|
|
if i.max_weight > p then
|
|
btype = b
|
|
break
|
|
end
|
|
end
|
|
if mg_villages.BUILDINGS[btype].pervillage ~= nil then
|
|
local n = 0
|
|
for j=1, #l do
|
|
if l[j].btype == btype then
|
|
n = n + 1
|
|
end
|
|
end
|
|
if n >= mg_villages.BUILDINGS[btype].pervillage then
|
|
goto choose
|
|
end
|
|
end
|
|
local rotation
|
|
if mg_villages.BUILDINGS[btype].no_rotate then
|
|
rotation = 0
|
|
else
|
|
rotation = pr:next(0, 3)
|
|
end
|
|
bsizex = mg_villages.BUILDINGS[btype].sizex
|
|
bsizez = mg_villages.BUILDINGS[btype].sizez
|
|
if rotation%2 == 1 then
|
|
bsizex, bsizez = bsizez, bsizex
|
|
end
|
|
if dist_center2(bx-vx, bsizex, bz-vz, bsizez)>vs*vs then goto out end
|
|
for _, a in ipairs(l) do
|
|
if math.abs(bx-a.x)<=(bsizex+a.bsizex)/2+2 and math.abs(bz-a.z)<=(bsizez+a.bsizez)/2+2 then goto out end
|
|
end
|
|
l[#l+1] = {x=bx, y=vh, z=bz, btype=btype, bsizex=bsizex, bsizez=bsizez, brotate = rotation}
|
|
::out::
|
|
end
|
|
return l]=]--
|
|
local rz = vz
|
|
while mg_villages.inside_village(rx, rz, village, vnoise) do
|
|
rx = rx - 1
|
|
end
|
|
rx = rx + 5
|
|
calls = {index = 1}
|
|
-- the function below is recursive; we need a way to count roads
|
|
mg_villages.road_nr = 0;
|
|
generate_road(village, l, pr, mg_villages.FIRST_ROADSIZE, rx, rz, 1, 0, vnoise, space_between_buildings)
|
|
i = 1
|
|
while i < calls.index do
|
|
generate_road(unpack(calls[i]))
|
|
i = i+1
|
|
end
|
|
mg_villages.road_nr = 0;
|
|
return l
|
|
end
|
|
|
|
|
|
-- dirt roads seperate the wheat area around medieval villages into seperate fields and make it look better
|
|
mg_villages.generate_dirt_roads = function( village, vnoise, bpos, secondary_dirt_roads )
|
|
local dirt_roads = {};
|
|
if( not( secondary_dirt_roads)) then
|
|
return dirt_roads;
|
|
end
|
|
for _, pos in ipairs( bpos ) do
|
|
|
|
local x = pos.x;
|
|
local z = pos.z;
|
|
local sizex = pos.bsizex;
|
|
local sizez = 2;
|
|
local orientation = 0;
|
|
-- prolong the roads; start with a 3x2 piece of road for testing
|
|
if( pos.btype == 'road' ) then
|
|
-- the road streches in x direction
|
|
if( pos.bsizex > pos.bsizez ) then
|
|
sizex = 3; -- start with a road of length 3
|
|
sizez = 2;
|
|
vx = -1; vz = 0; vsx = 1; vsz = 0;
|
|
x = pos.x - sizex;
|
|
z = pos.z + math.floor((pos.bsizez-2)/2); -- aim for the middle of the road
|
|
orientation = 0;
|
|
-- if it is not possible to prolong the road at one end, then try the other
|
|
if( not( placeable( x, z, sizex, sizez, bpos, false, nil))) then
|
|
x = pos.x + pos.bsizex;
|
|
vx = 0;
|
|
orientation = 2;
|
|
end
|
|
-- the road stretches in z direction
|
|
else
|
|
sizex = 2;
|
|
sizez = 3;
|
|
vx = 0; vz = -1; vsx = 0; vsz = 1;
|
|
x = pos.x + math.floor((pos.bsizex-2)/2); -- aim for the middle of the road
|
|
z = pos.z - sizez;
|
|
orientation = 1;
|
|
if( not( placeable( x, z, sizex, sizez, bpos, false, nil))) then
|
|
z = pos.z + pos.bsizez;
|
|
vz = 0;
|
|
orientation = 3;
|
|
end
|
|
end
|
|
|
|
else
|
|
if( pos.o == 0 ) then
|
|
x = pos.x-pos.side;
|
|
z = pos.z-2;
|
|
sizex = pos.bsizex+1;
|
|
sizez = 2;
|
|
vx = 0; vz = 0; vsx = 1; vsz = 0;
|
|
|
|
elseif( pos.o == 2 ) then
|
|
x = pos.x-pos.side+2;
|
|
z = pos.z-2;
|
|
sizex = pos.bsizex+1;
|
|
sizez = 2;
|
|
vx = -1; vz = 0; vsx = 1; vsz = 0;
|
|
|
|
elseif( pos.o == 1 ) then
|
|
x = pos.x-2;
|
|
z = pos.z-pos.side+2;
|
|
sizex = 2;
|
|
sizez = pos.bsizez+1;
|
|
vx = 0; vz = -1; vsx = 0; vsz = 1;
|
|
|
|
elseif( pos.o == 3 ) then
|
|
x = pos.x-2;
|
|
z = pos.z-pos.side;
|
|
sizex = 2;
|
|
sizez = pos.bsizez+1;
|
|
vx = 0; vz = 0; vsx = 0; vsz = 1;
|
|
end
|
|
orientation = pos.o;
|
|
|
|
end
|
|
|
|
-- prolong the dirt road by 1
|
|
while( placeable( x, z, sizex, sizez, bpos, false, nil)
|
|
and placeable( x, z, sizex, sizez, dirt_roads, false, orientation)
|
|
and mg_villages.inside_village_area(x, z, village, vnoise)
|
|
and mg_villages.inside_village_area(x+sizex, z+sizez, village, vnoise)) do
|
|
sizex = sizex + vsx;
|
|
sizez = sizez + vsz;
|
|
x = x + vx;
|
|
z = z + vz;
|
|
end
|
|
|
|
-- the dirt road may exceed the village boundaries slightly, but it may not interfere with other buildings
|
|
if( not( placeable( x, z, sizex, sizez, bpos, false, nil))
|
|
or not( placeable( x, z, sizex, sizez, dirt_roads, false, orientation))) then
|
|
sizex = sizex - vsx;
|
|
sizez = sizez - vsz;
|
|
x = x - vx;
|
|
z = z - vz;
|
|
end
|
|
|
|
if( placeable( x, z, sizex, sizez, bpos, false, nil)
|
|
and placeable( x, z, sizex, sizez, dirt_roads, false, orientation)) then
|
|
dirt_roads[#dirt_roads+1] = {x=x, y=village.vh, z=z, btype="dirt_road", bsizex=sizex, bsizez=sizez, brotate = 0, o=orientation}
|
|
end
|
|
end
|
|
return dirt_roads;
|
|
end
|
|
|
|
|
|
|
|
-- they don't all grow cotton; farming_plus fruits are far more intresting!
|
|
-- Note: This function modifies replacements.ids and replacements.table for each building
|
|
-- as far as fruits are concerned. It needs to be called before placing a building
|
|
-- which contains fruits.
|
|
-- The function might as well be a local one.
|
|
mg_villages.get_fruit_replacements = function( replacements, fruit)
|
|
|
|
if( not( fruit )) then
|
|
return;
|
|
end
|
|
|
|
for i=1,8 do
|
|
local old_name = '';
|
|
local new_name = '';
|
|
-- farming_plus plants sometimes come in 3 or 4 variants, but not in 8 as cotton does
|
|
if( minetest.registered_nodes[ 'farming_plus:'..fruit..'_'..i ]) then
|
|
old_name = "farming:cotton_"..i;
|
|
new_name = 'farming_plus:'..fruit..'_'..i;
|
|
|
|
-- "surplus" cotton variants will be replaced with the full grown fruit
|
|
elseif( minetest.registered_nodes[ 'farming_plus:'..fruit ]) then
|
|
old_name = "farming:cotton_"..i;
|
|
new_name = 'farming_plus:'..fruit;
|
|
|
|
-- and plants from farming: are supported as well
|
|
elseif( minetest.registered_nodes[ 'farming:'..fruit..'_'..i ]) then
|
|
old_name = "farming:cotton_"..i;
|
|
new_name = 'farming:'..fruit..'_'..i;
|
|
|
|
elseif( minetest.registered_nodes[ 'farming:'..fruit ]) then
|
|
old_name = "farming:cotton_"..i;
|
|
new_name = 'farming:'..fruit;
|
|
end
|
|
|
|
if( old_name ~= '' and new_name ~= '' ) then
|
|
-- this is mostly used by the voxelmanip based spawning of .we files
|
|
replacements.ids[ minetest.get_content_id( old_name )] = minetest.get_content_id( new_name );
|
|
-- this is used by the place_schematic based spawning
|
|
for i,v in ipairs( replacements.table ) do
|
|
if( v and #v and v[1]==old_name ) then
|
|
v[2] = new_name;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- either uses get_node_or_nil(..) or the data from voxelmanip
|
|
-- the function might as well be local (only used by *.mg_drop_moresnow)
|
|
mg_villages.get_node_somehow = function( x, y, z, a, data, param2_data )
|
|
if( a and data and param2_data ) then
|
|
return { content = data[a:index(x, y, z)], param2 = param2_data[a:index(x, y, z)] };
|
|
end
|
|
-- no voxelmanip; get the node the normal way
|
|
local node = minetest.get_node_or_nil( {x=x, y=y, z=z} );
|
|
if( not( node ) ) then
|
|
return { content = moresnow.c_ignore, param2 = 0 };
|
|
end
|
|
return { content = minetest.get_content_id( node.name ), param2 = node.param2, name = node.name };
|
|
end
|
|
|
|
|
|
-- "drop" moresnow snow on diffrent shapes; works for voxelmanip and node-based setting
|
|
mg_villages.mg_drop_moresnow = function( x, z, y_top, y_bottom, a, data, param2_data)
|
|
|
|
-- this only works if moresnow is installed
|
|
if( not( moresnow ) or not( moresnow.suggest_snow_type )) then
|
|
return;
|
|
end
|
|
|
|
local y = y_top;
|
|
local node_above = mg_villages.get_node_somehow( x, y+1, z, a, data, param2_data );
|
|
local node_below = nil;
|
|
while( y >= y_bottom ) do
|
|
|
|
node_below = mg_villages.get_node_somehow( x, y, z, a, data, param2_data );
|
|
if( node_above.content == moresnow.c_air
|
|
and node_below.content
|
|
and node_below.content ~= moresnow.c_ignore
|
|
and node_below.content ~= moresnow.c_air ) then
|
|
|
|
-- if the node below drops snow when digged (i.e. is either snow or a moresnow node), we're finished
|
|
local get_drop = minetest.get_name_from_content_id( node_below.content );
|
|
if( get_drop ) then
|
|
get_drop = minetest.registered_nodes[ get_drop ];
|
|
if( get_drop and get_drop.drop and type( get_drop.drop )=='string' and get_drop.drop == 'default:snow') then
|
|
return;
|
|
end
|
|
end
|
|
if( not(node_below.content)
|
|
or node_below.content == mg_villages.road_node
|
|
or node_below.content == moresnow.c_snow ) then
|
|
return;
|
|
end
|
|
|
|
local suggested = moresnow.suggest_snow_type( node_below.content, node_below.param2 );
|
|
|
|
-- c_snow_top and c_snow_fence can only exist when the node 2 below is a solid one
|
|
if( suggested.new_id == moresnow.c_snow_top
|
|
or suggested.new_id == moresnow.c_snow_fence) then
|
|
local node_below2 = mg_villages.get_node_somehow( x, y-1, z, a, data, param2_data);
|
|
if( node_below2.content ~= moresnow.c_ignore
|
|
and node_below2.content ~= moresnow.c_air ) then
|
|
local suggested2 = moresnow.suggest_snow_type( node_below2.content, node_below2.param2 );
|
|
|
|
if( suggested2.new_id == moresnow.c_snow ) then
|
|
return { height = y+1, suggested = suggested };
|
|
end
|
|
end
|
|
-- it is possible that this is not the right shape; if so, the snow will continue to fall down
|
|
elseif( suggested.new_id ~= moresnow.c_ignore ) then
|
|
|
|
return { height = y+1, suggested = suggested };
|
|
end
|
|
-- TODO return; -- abort; there is no fitting moresnow shape for the node below
|
|
end
|
|
y = y-1;
|
|
node_above = node_below;
|
|
end
|
|
end
|
|
|
|
|
|
|
|
local function generate_building(pos, minp, maxp, data, param2_data, a, pr, extranodes, replacements)
|
|
local binfo = mg_villages.BUILDINGS[pos.btype]
|
|
local scm
|
|
|
|
-- schematics of .mts type are not handled here; they need to be placed using place_schematics
|
|
if( binfo.is_mts == 1 ) then
|
|
return;
|
|
end
|
|
|
|
if( type(binfo.scm) == "string" and binfo.scm_data_cache and type(binfo.scm_data_cache)=="string" )then
|
|
scm = minetest.deserialize( binfo.scm_data_cache ); --mg_villages.import_scm(binfo.scm, replacements)
|
|
-- at first time of spawning, all nodes ought to be defined; thus, we can cache the data
|
|
elseif( type( binfo.scm ) == "string" ) then
|
|
scm = mg_villages.import_scm( mg_villages.BUILDINGS[ pos.btype ].scm );
|
|
mg_villages.BUILDINGS[ pos.btype ].scm_data_cache = minetest.serialize( scm )
|
|
else
|
|
scm = binfo.scm
|
|
end
|
|
scm = mg_villages.rotate_scm(scm, pos.brotate)
|
|
|
|
|
|
-- the fruit is set per building, not per village as the other replacements
|
|
if( binfo.farming_plus and binfo.farming_plus == 1 and pos.fruit ) then
|
|
mg_villages.get_fruit_replacements( replacements, pos.fruit);
|
|
end
|
|
|
|
local c_ignore = minetest.get_content_id("ignore")
|
|
local c_air = minetest.get_content_id("air")
|
|
local c_snow = minetest.get_content_id( "default:snow");
|
|
local c_dirt = minetest.get_content_id( "default:dirt" );
|
|
local c_dirt_with_grass = minetest.get_content_id( "default:dirt_with_grass" );
|
|
local c_dirt_with_snow = minetest.get_content_id( "default:dirt_with_snow" );
|
|
for x = 0, pos.bsizex-1 do
|
|
for z = 0, pos.bsizez-1 do
|
|
local has_snow = false;
|
|
local ground_type = c_dirt_with_grass;
|
|
for y = 0, binfo.ysize-1 do
|
|
ax, ay, az = pos.x+x, pos.y+y+binfo.yoff, pos.z+z
|
|
if (ax >= minp.x and ax <= maxp.x) and (ay >= minp.y and ay <= maxp.y) and (az >= minp.z and az <= maxp.z) then
|
|
|
|
t = scm[y+1][x+1][z+1]
|
|
|
|
if( binfo.yoff+y == 0 ) then
|
|
local node_content = data[a:index(ax, ay, az)];
|
|
-- no snow on the gravel roads
|
|
if( node_content == c_dirt_with_snow or data[a:index(ax, ay+1, az)]==c_snow) then
|
|
has_snow = true;
|
|
end
|
|
|
|
ground_type = node_content;
|
|
end
|
|
|
|
if type(t) == "table" then
|
|
-- handle extranodes
|
|
if t.extranode then
|
|
-- do replacements for extranodes; those are name-based
|
|
if( replacements.table[ t.node.name ]) then
|
|
t.node.name = replacements.table[ t.node.name ];
|
|
end
|
|
-- in case such a node is replaced with dirt, just proceed
|
|
if( t.node.name == 'default:dirt' or t.node.name == 'default:dirt_with_grass' ) then
|
|
t.node.content = ground_type;
|
|
-- else the data will be stored and added later on
|
|
else
|
|
table.insert(extranodes, {node = t.node, meta = t.meta, pos = {x = ax, y = ay, z = az}})
|
|
end
|
|
else
|
|
-- do replacements for normal nodes with facedir or wallmounted
|
|
if( replacements.ids[ t.node.content ]) then
|
|
t.node.content = replacements.ids[ t.node.content ];
|
|
end
|
|
-- replace all dirt and dirt with grass at that x,z coordinate with the stored ground grass node;
|
|
-- this case here applies only to nodes which where facedir/wallmounted prior to their above replacement
|
|
if( t.node.content == c_dirt or t.node.content == c_dirt_with_grass ) then
|
|
t.node.content = ground_type;
|
|
end
|
|
end
|
|
data[ a:index(ax, ay, az)] = t.node.content;
|
|
param2_data[a:index(ax, ay, az)] = t.node.param2;
|
|
-- air and gravel
|
|
elseif t ~= c_ignore then
|
|
|
|
if( t and replacements.ids[ t ] ) then
|
|
t = replacements.ids[ t ];
|
|
end
|
|
if( t and t==c_dirt or t==c_dirt_with_grass ) then
|
|
t = ground_type;
|
|
end
|
|
data[a:index(ax, ay, az)] = t
|
|
end
|
|
end
|
|
end
|
|
|
|
local ax = pos.x + x;
|
|
local az = pos.z + z;
|
|
local y_top = pos.y+binfo.yoff+binfo.ysize;
|
|
if( y_top+1 > maxp.y ) then
|
|
y_top = maxp.y-1;
|
|
end
|
|
local y_bottom = pos.y+binfo.yoff;
|
|
if( y_bottom < minp.y ) then
|
|
y_bottom = minp.y;
|
|
end
|
|
if( has_snow and ax >= minp.x and ax <= maxp.x and az >= minp.z and az <= maxp.z ) then
|
|
local res = mg_villages.mg_drop_moresnow( ax, az, y_top, y_bottom, a, data, param2_data);
|
|
if( res ) then
|
|
data[ a:index(ax, res.height, az)] = res.suggested.new_id;
|
|
param2_data[a:index(ax, res.height, az)] = res.suggested.param2;
|
|
has_snow = false;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- similar to generate_building, except that it uses minetest.place_schematic(..) instead of changing voxelmanip data;
|
|
-- this has advantages for nodes that use facedir;
|
|
-- the function is called AFTER the mapgen data has been written in init.lua
|
|
-- Function is called from init.lua.
|
|
mg_villages.place_schematics = function( bpos, replacements, voxelarea, pr )
|
|
|
|
local mts_path = mg_villages.modpath.."/schems/";
|
|
|
|
for _, pos in ipairs( bpos ) do
|
|
|
|
local binfo = mg_villages.BUILDINGS[pos.btype];
|
|
|
|
|
|
-- We need to check all 8 corners of the building.
|
|
-- This will only work for buildings that are smaller than chunk size (relevant here: about 111 nodes)
|
|
-- The function only spawns buildings which are at least partly contained in this chunk/voxelarea.
|
|
if( voxelarea
|
|
and ( voxelarea:contains( pos.x, pos.y - binfo.yoff, pos.z )
|
|
or voxelarea:contains( pos.x + pos.bsizex, pos.y - binfo.yoff, pos.z )
|
|
or voxelarea:contains( pos.x, pos.y - binfo.yoff, pos.z + pos.bsizez )
|
|
or voxelarea:contains( pos.x + pos.bsizex, pos.y - binfo.yoff, pos.z + pos.bsizez )
|
|
or voxelarea:contains( pos.x, pos.y - binfo.yoff + binfo.ysize, pos.z )
|
|
or voxelarea:contains( pos.x + pos.bsizex, pos.y - binfo.yoff + binfo.ysize, pos.z )
|
|
or voxelarea:contains( pos.x, pos.y - binfo.yoff + binfo.ysize, pos.z + pos.bsizez )
|
|
or voxelarea:contains( pos.x + pos.bsizex, pos.y - binfo.yoff + binfo.ysize, pos.z + pos.bsizez ) )) then
|
|
|
|
-- that function places schematics, adds snow where needed
|
|
-- and the grass type used directly in the pos/bpos data structure
|
|
mg_villages.place_one_schematic( bpos, replacements, pos, mts_path );
|
|
end
|
|
end
|
|
--print('VILLAGE DATA: '..minetest.serialize( bpos ));
|
|
end
|
|
|
|
|
|
-- also adds a snow layer for buildings spawned from .we files
|
|
-- function might as well be local
|
|
mg_villages.place_one_schematic = function( bpos, replacements, pos, mts_path )
|
|
|
|
-- just for the record: count how many times this building has been placed already;
|
|
-- multiple placements are commen at chunk boundaries (may be up to 8 placements)
|
|
if( not( pos.count_placed )) then
|
|
pos.count_placed = 1;
|
|
else
|
|
pos.count_placed = pos.count_placed + 1;
|
|
end
|
|
|
|
local binfo = mg_villages.BUILDINGS[pos.btype];
|
|
|
|
local start_pos = { x=( pos.x ), y=(pos.y + binfo.yoff ), z=( pos.z )};
|
|
local end_pos = { x=( pos.x+pos.bsizex), y=(pos.y + binfo.yoff + binfo.ysize), z=( pos.z + pos.bsizez )};
|
|
|
|
-- this function is only responsible for files that are in .mts format
|
|
if( binfo.is_mts == 1 ) then
|
|
-- translate rotation
|
|
local rotation = 0;
|
|
if( pos.brotate == 1 ) then
|
|
rotation = 90;
|
|
elseif( pos.brotate == 2 ) then
|
|
rotation = 180;
|
|
elseif( pos.brotate == 3 ) then
|
|
rotation = 270;
|
|
else
|
|
rotation = 0;
|
|
end
|
|
if( binfo.rotated ) then
|
|
rotation = (rotation + binfo.rotated ) % 360;
|
|
end
|
|
|
|
-- the fruit is set per building, not per village as the other replacements
|
|
if( binfo.farming_plus and binfo.farming_plus == 1 and pos.fruit ) then
|
|
mg_villages.get_fruit_replacements( replacements, pos.fruit);
|
|
end
|
|
|
|
-- find out which ground types are used and where we need to place snow later on
|
|
local ground_types = {};
|
|
local has_snow = {};
|
|
for x = start_pos.x, end_pos.x do
|
|
for z = start_pos.z, end_pos.z do
|
|
-- store which particular grass type (or sand/desert sand or whatever) was there before placing the building
|
|
local node = minetest.get_node( {x=x, y=pos.y, z=z} );
|
|
if( node and node.name and node.name ~= 'ignore' and node.name ~= 'air'
|
|
and node.name ~= 'default:dirt' and node.name ~= 'default:dirt_with_grass') then
|
|
ground_types[ tostring(x)..':'..tostring(z) ] = node.name;
|
|
end
|
|
-- find out if there is snow above
|
|
node = minetest.get_node( {x=x, y=(pos.y+1), z=z} );
|
|
local node2 = minetest.get_node({x=x, y=(start_pos.y-2), z=z} );
|
|
if( node and node.name and node.name == 'default:snow' ) then
|
|
has_snow[ tostring(x)..':'..tostring(z) ] = true; -- any value would do here; just has to be defined
|
|
-- place snow as a marker one below the building
|
|
minetest.swap_node( {x=x, y=(start_pos.y-2), z=z}, {name='default:dirt_with_snow'});
|
|
-- read the marker (the building might have been placed once already)
|
|
elseif( node2 and node2.name and node2.name == 'default:dirt_with_snow' ) then
|
|
has_snow[ tostring(x)..':'..tostring(z) ] = true;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- print( 'PLACED BUILDING '..tostring( binfo.scm )..' AT '..minetest.pos_to_string( pos )..'. Max. size: '..tostring( max_xz )..' grows: '..tostring(fruit));
|
|
-- force placement (we want entire buildings)
|
|
minetest.place_schematic( start_pos, mts_path..binfo.scm..'.mts', tostring( rotation ), replacements, true);
|
|
|
|
-- call on_construct for all the nodes that require it (i.e. furnaces)
|
|
for i, v in ipairs( binfo.on_constr ) do
|
|
|
|
-- there are only very few nodes which need this special treatment
|
|
local nodes = minetest.find_nodes_in_area( start_pos, end_pos, v);
|
|
|
|
for _, p in ipairs( nodes ) do
|
|
minetest.registered_nodes[ v ].on_construct( p );
|
|
end
|
|
end
|
|
|
|
-- note: after_place_node is not handled here because we do not have a player at hand that could be used for it
|
|
|
|
-- evry dirt_with_grass node gets replaced with the grass type at that location
|
|
-- (as long as it was something else than dirt_with_grass)
|
|
local dirt_nodes = minetest.find_nodes_in_area( start_pos, end_pos, {'default:dirt_with_grass'} );
|
|
for _,p in ipairs( dirt_nodes ) do
|
|
local new_type = ground_types[ tostring( p.x )..':'..tostring( p.z ) ];
|
|
if( new_type ) then
|
|
-- minetest.set_node( p, { name = new_type } );
|
|
end
|
|
end
|
|
|
|
-- add snow on roofs, slabs, stairs, fences, ...
|
|
for x = start_pos.x, end_pos.x do
|
|
for z = start_pos.z, end_pos.z do
|
|
if( moresnow and moresnow.suggest_snow_type and has_snow[ tostring(x)..':'..tostring(z) ] ) then
|
|
|
|
local res = mg_villages.mg_drop_moresnow( x, z, end_pos.y, start_pos.y, nil, nil, nil );
|
|
if( res ) then
|
|
minetest.swap_node( {x=x, y=res.height, z=z},
|
|
{ name=minetest.get_name_from_content_id( res.suggested.new_id ), param2=res.suggested.param2 });
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- TODO: fill chests etc.
|
|
end
|
|
|
|
|
|
|
|
|
|
local MIN_DIST = 1
|
|
|
|
local function pos_far_buildings(x, z, l)
|
|
for _, a in ipairs(l) do
|
|
if a.x - MIN_DIST <= x and x <= a.x + a.bsizex + MIN_DIST and
|
|
a.z - MIN_DIST <= z and z <= a.z + a.bsizez + MIN_DIST then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
|
|
local function generate_walls(bpos, data, a, minp, maxp, vh, vx, vz, vs, vnoise)
|
|
for x = minp.x, maxp.x do
|
|
for z = minp.z, maxp.z do
|
|
local xx = (vnoise:get2d({x=x, y=z})-2)*20+(40/(vs*vs))*((x-vx)*(x-vx)+(z-vz)*(z-vz))
|
|
if xx>=40 and xx <= 44 then
|
|
bpos[#bpos+1] = {x=x, z=z, y=vh, btype="wall", bsizex=1, bsizez=1, brotate=0}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- determine which building is to be placed where
|
|
-- also choose which blocks to replace with which other blocks (to make villages more intresting)
|
|
mg_villages.generate_village = function(village, vnoise)
|
|
local vx, vz, vs, vh = village.vx, village.vz, village.vs, village.vh
|
|
local village_type = village.village_type;
|
|
local seed = mg_villages.get_bseed({x=vx, z=vz})
|
|
local pr_village = PseudoRandom(seed)
|
|
|
|
-- generate a name for the village
|
|
village.name = namegen.generate_village_name( pr_village );
|
|
|
|
-- only generate a new village if the data is not already stored
|
|
-- (the algorithm is fast, but village types and houses which are available may change later on,
|
|
-- and that might easily cause chaos if the village is generated again with diffrent input)
|
|
if( village.to_add_data and village.to_add_data.bpos and village.to_add_data.replacements and village.to_add_data.plantlist) then
|
|
--print('VILLAGE GENREATION: USING ALREADY GENERATED VILLAGE: Nr. '..tostring( village.nr ));
|
|
return;
|
|
end
|
|
|
|
-- in the case of medieval villages, we later on want to add wheat fields with dirt roads; 1 wide dirt roads look odd
|
|
local space_between_buildings = mg_villages.village_sizes[ village_type ].space_between_buildings;
|
|
|
|
-- actually generate the village structure
|
|
local bpos = generate_bpos( village, pr_village, vnoise, space_between_buildings)
|
|
|
|
|
|
local secondary_dirt_roads = nil;
|
|
-- if there is enough space, add dirt roads between the buildings (those will later be prolonged so that they reach the fields)
|
|
if( space_between_buildings >= 2 and village_type == 'medieval') then
|
|
secondary_dirt_roads = "dirt_road";
|
|
end
|
|
|
|
local dirt_roads = mg_villages.generate_dirt_roads( village, vnoise, bpos, secondary_dirt_roads );
|
|
|
|
-- set fruits for all buildings in the village that need it - regardless weather they will be spawned
|
|
-- now or later; after the first call to this function here, the village data will be final
|
|
for _, pos in ipairs( bpos ) do
|
|
local binfo = mg_villages.BUILDINGS[pos.btype];
|
|
if( binfo.farming_plus and binfo.farming_plus == 1 and mg_villages.fruit_list and not pos.furit) then
|
|
pos.fruit = mg_villages.fruit_list[ pr_village:next( 1, #mg_villages.fruit_list )];
|
|
end
|
|
end
|
|
|
|
-- a changing replacement list would also be pretty confusing
|
|
local p = PseudoRandom(seed);
|
|
-- if the village is new, replacement_list is nil and a new replacement list will be created
|
|
local replacements = mg_villages.get_replacement_table( village.village_type, p, nil );
|
|
|
|
-- determine which plants will grow in the area around the village
|
|
local plantlist = {};
|
|
local sapling_id = replacements.table[ 'default:sapling' ];
|
|
if( not( sapling_id )) then
|
|
sapling_id = 'default:sapling';
|
|
end
|
|
sapling_id = minetest.get_content_id( sapling_id );
|
|
-- 1/sapling_p = probability of a sapling beeing placed
|
|
local sapling_p = 25;
|
|
if( mg_villages.sapling_probability[ sapling_id ] ) then
|
|
sapling_p = mg_villages.sapling_probability[ sapling_id ];
|
|
end
|
|
|
|
-- medieval villages are sourrounded by wheat fields
|
|
if( village_type == 'medieval' ) then
|
|
local c_wheat = minetest.get_content_id( 'farming:wheat_8');
|
|
plantlist = {
|
|
{ id=sapling_id, p=sapling_p*10 }, -- trees are rather rare
|
|
{ id=c_wheat, p=1 }};
|
|
-- lumberjack camps have handy trees nearby
|
|
elseif( village_type == 'lumberjack' ) then
|
|
local c_junglegrass = minetest.get_content_id( 'default:junglegrass');
|
|
plantlist = {
|
|
{ id=sapling_id, p=sapling_p },
|
|
{ id=c_junglegrass, p=25 }};
|
|
-- the villages of type taoki grow cotton
|
|
elseif( village_type == 'taoki' ) then
|
|
local c_cotton = minetest.get_content_id( 'farming:cotton_8');
|
|
plantlist = {
|
|
{ id=sapling_id, p=sapling_p*5 }, -- not too many trees
|
|
{ id=c_cotton, p=1 }};
|
|
-- default/fallback: grassland
|
|
else
|
|
local c_grass = minetest.get_content_id( 'default:grass_5');
|
|
plantlist = {
|
|
{ id=sapling_id, p=sapling_p*10}, -- only few trees
|
|
{ id=c_grass, p=3 }};
|
|
end
|
|
|
|
-- store the generated data in the village table
|
|
village.to_add_data = {};
|
|
village.to_add_data.bpos = bpos;
|
|
village.to_add_data.replacements = replacements.list;
|
|
village.to_add_data.dirt_roads = dirt_roads;
|
|
village.to_add_data.plantlist = plantlist;
|
|
|
|
--print('VILLAGE GENREATION: GENERATING NEW VILLAGE Nr. '..tostring( village.nr ));
|
|
end
|
|
|
|
|
|
-- actually place the buildings (at least those which came as .we files; .mts files are handled later on)
|
|
-- this code is also responsible for tree placement
|
|
mg_villages.place_buildings = function(village, minp, maxp, data, param2_data, a, vnoise)
|
|
local vx, vz, vs, vh = village.vx, village.vz, village.vs, village.vh
|
|
local village_type = village.village_type;
|
|
local seed = mg_villages.get_bseed({x=vx, z=vz})
|
|
|
|
local bpos = village.to_add_data.bpos;
|
|
|
|
village.to_grow = {}; -- TODO this is a temporal solution to avoid flying tree trunks
|
|
--generate_walls(bpos, data, a, minp, maxp, vh, vx, vz, vs, vnoise)
|
|
local pr = PseudoRandom(seed)
|
|
for _, g in ipairs(village.to_grow) do
|
|
if pos_far_buildings(g.x, g.z, bpos) then
|
|
mg.registered_trees[g.id].grow(data, a, g.x, g.y, g.z, minp, maxp, pr)
|
|
end
|
|
end
|
|
|
|
local replacements = mg_villages.get_replacement_table( village.village_type, p, village.to_add_data.replacements );
|
|
|
|
local extranodes = {}
|
|
for _, pos in ipairs(bpos) do
|
|
-- replacements are in table format for mapgen-based building spawning
|
|
generate_building(pos, minp, maxp, data, param2_data, a, pr_village, extranodes, replacements )
|
|
end
|
|
|
|
-- replacements are in list format for minetest.place_schematic(..) type spawning
|
|
return { extranodes = extranodes, bpos = bpos, replacements = replacements.list, dirt_roads = village.to_add_data.dirt_roads,
|
|
plantlist = village.to_add_data.plantlist };
|
|
end
|
|
|
|
|
|
-- add the dirt roads
|
|
mg_villages.place_dirt_roads = function(village, minp, maxp, data, param2_data, a, vnoise, c_road_node)
|
|
local c_air = minetest.get_content_id( 'air' );
|
|
for _, pos in ipairs(village.to_add_data.dirt_roads) do
|
|
local param2 = 0;
|
|
if( pos.bsizex > 2 ) then
|
|
param2 = 1;
|
|
end
|
|
for x = 0, pos.bsizex-1 do
|
|
for z = 0, pos.bsizez-1 do
|
|
local ax = pos.x+x;
|
|
local az = pos.z+z;
|
|
|
|
if (ax >= minp.x and ax <= maxp.x) and (ay >= minp.y and ay <= maxp.y) and (az >= minp.z and az <= maxp.z) then
|
|
-- roads have a height of 1 block
|
|
data[ a:index( ax, pos.y, az)] = c_road_node;
|
|
param2_data[ a:index( ax, pos.y, az)] = param2;
|
|
-- ...with air above
|
|
data[ a:index( ax, pos.y+1, az)] = c_air;
|
|
data[ a:index( ax, pos.y+2, az)] = c_air;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|