2022-06-10 02:40:33 +02:00
mcl_structures.registered_structures = { }
2022-10-29 00:18:48 +02:00
2022-10-24 07:07:56 +02:00
local place_queue = { }
2022-08-09 19:28:30 +02:00
local disabled_structures = minetest.settings : get ( " mcl_disabled_structures " )
if disabled_structures then disabled_structures = disabled_structures : split ( " , " )
else disabled_structures = { } end
2022-10-29 00:18:48 +02:00
local peaceful = minetest.settings : get_bool ( " only_peaceful_mobs " , false )
2022-10-29 00:43:15 +02:00
local mob_cap_player = tonumber ( minetest.settings : get ( " mcl_mob_cap_player " ) ) or 75
2022-10-29 01:26:32 +02:00
local mob_cap_animal = tonumber ( minetest.settings : get ( " mcl_mob_cap_animal " ) ) or 10
2022-10-29 00:18:48 +02:00
2022-09-13 01:14:02 +02:00
local logging = minetest.settings : get_bool ( " mcl_logging_structures " , true )
2022-10-29 00:43:15 +02:00
local mg_name = minetest.get_mapgen_setting ( " mg_name " )
2022-10-24 05:26:47 +02:00
local rotations = {
" 0 " ,
" 90 " ,
" 180 " ,
" 270 "
}
2022-08-09 19:28:30 +02:00
function mcl_structures . is_disabled ( structname )
2022-09-11 23:44:33 +02:00
return table.indexof ( disabled_structures , structname ) ~= - 1
2022-08-09 19:28:30 +02:00
end
2022-06-24 23:24:51 +02:00
2022-10-24 05:26:47 +02:00
local function ecb_place ( blockpos , action , calls_remaining , param )
if calls_remaining >= 1 then return end
minetest.place_schematic ( param.pos , param.schematic , param.rotation , param.replacements , param.force_placement , param.flags )
if param.after_placement_callback and param.p1 and param.p2 then
param.after_placement_callback ( param.p1 , param.p2 , param.size , param.rotation , param.pr , param.callback_param )
end
end
function mcl_structures . place_schematic ( pos , schematic , rotation , replacements , force_placement , flags , after_placement_callback , pr , callback_param )
2022-10-28 13:34:58 +02:00
if type ( schematic ) ~= " table " and not mcl_util.file_exists ( schematic ) then
minetest.log ( " warning " , " [mcl_structures] schematic file " .. tostring ( schematic ) .. " does not exist. " )
2022-10-28 01:02:48 +02:00
return end
2022-10-24 05:26:47 +02:00
local s = loadstring ( minetest.serialize_schematic ( schematic , " lua " , { lua_use_comments = false , lua_num_indent_spaces = 0 } ) .. " return schematic " ) ( )
if s and s.size then
local x , z = s.size . x , s.size . z
if rotation then
if rotation == " random " and pr then
rotation = rotations [ pr : next ( 1 , # rotations ) ]
end
if rotation == " random " then
x = math.max ( x , z )
z = x
elseif rotation == " 90 " or rotation == " 270 " then
x , z = z , x
end
end
local p1 = { x = pos.x , y = pos.y , z = pos.z }
local p2 = { x = pos.x + x - 1 , y = pos.y + s.size . y - 1 , z = pos.z + z - 1 }
minetest.log ( " verbose " , " [mcl_structures] size= " .. minetest.pos_to_string ( s.size ) .. " , rotation= " .. tostring ( rotation ) .. " , emerge from " .. minetest.pos_to_string ( p1 ) .. " to " .. minetest.pos_to_string ( p2 ) )
local param = { pos = vector.new ( pos ) , schematic = s , rotation = rotation , replacements = replacements , force_placement = force_placement , flags = flags , p1 = p1 , p2 = p2 , after_placement_callback = after_placement_callback , size = vector.new ( s.size ) , pr = pr , callback_param = callback_param }
minetest.emerge_area ( p1 , p2 , ecb_place , param )
return true
end
end
function mcl_structures . get_struct ( file )
local localfile = modpath .. " /schematics/ " .. file
local file , errorload = io.open ( localfile , " rb " )
if errorload then
minetest.log ( " error " , " [mcl_structures] Could not open this struct: " .. localfile )
return nil
end
local allnode = file : read ( " *a " )
file : close ( )
return allnode
end
-- Call on_construct on pos.
-- Useful to init chests from formspec.
local function init_node_construct ( pos )
local node = minetest.get_node ( pos )
local def = minetest.registered_nodes [ node.name ]
if def and def.on_construct then
def.on_construct ( pos )
return true
end
return false
end
mcl_structures.init_node_construct = init_node_construct
2022-06-24 23:24:51 +02:00
function mcl_structures . fill_chests ( p1 , p2 , loot , pr )
for it , lt in pairs ( loot ) do
2022-06-24 03:36:21 +02:00
local nodes = minetest.find_nodes_in_area ( p1 , p2 , it )
for _ , p in pairs ( nodes ) do
2022-06-24 23:24:51 +02:00
local lootitems = mcl_loot.get_multi_loot ( lt , pr )
2022-06-24 03:36:21 +02:00
mcl_structures.init_node_construct ( p )
local meta = minetest.get_meta ( p )
local inv = meta : get_inventory ( )
mcl_loot.fill_inventory ( inv , " main " , lootitems , pr )
end
end
end
2022-06-24 23:24:51 +02:00
local function generate_loot ( pos , def , pr )
2022-09-13 23:08:45 +02:00
local hl = def.sidelen
2022-06-24 23:24:51 +02:00
local p1 = vector.offset ( pos , - hl , - hl , - hl )
local p2 = vector.offset ( pos , hl , hl , hl )
if def.loot then mcl_structures.fill_chests ( p1 , p2 , def.loot , pr ) end
end
2022-10-15 03:39:27 +02:00
function mcl_structures . construct_nodes ( p1 , p2 , nodes )
local nn = minetest.find_nodes_in_area ( p1 , p2 , nodes )
2022-09-11 22:41:36 +02:00
for _ , p in pairs ( nn ) do
2022-09-11 23:44:33 +02:00
mcl_structures.init_node_construct ( p )
2022-09-11 22:41:36 +02:00
end
end
2022-10-15 03:39:27 +02:00
local function construct_nodes ( pos , def , pr )
2022-11-12 14:00:38 +01:00
return mcl_structures.construct_nodes ( vector.offset ( pos , - def.sidelen / 2 , 0 , - def.sidelen / 2 ) , vector.offset ( pos , def.sidelen / 2 , def.sidelen , def.sidelen / 2 ) , def.construct_nodes )
2022-10-15 03:39:27 +02:00
end
2022-06-24 23:24:51 +02:00
2022-06-24 14:02:29 +02:00
function mcl_structures . find_lowest_y ( pp )
local y = 31000
for _ , p in pairs ( pp ) do
if p.y < y then y = p.y end
end
return y
end
function mcl_structures . find_highest_y ( pp )
local y = - 31000
for _ , p in pairs ( pp ) do
if p.y > y then y = p.y end
end
return y
end
2022-08-01 01:43:23 +02:00
local function smooth_cube ( nn , pos , plane , amnt )
local r = { }
local amnt = amnt or 9
table.sort ( nn , function ( a , b )
if false or plane then
return vector.distance ( vector.new ( pos.x , 0 , pos.z ) , vector.new ( a.x , 0 , a.z ) ) < vector.distance ( vector.new ( pos.x , 0 , pos.z ) , vector.new ( b.x , 0 , b.z ) )
else
return vector.distance ( pos , a ) < vector.distance ( pos , b )
end
end )
for i = 1 , math.max ( 1 , # nn - amnt ) do table.insert ( r , nn [ i ] ) end
return r
end
2022-08-10 15:14:51 +02:00
local function find_ground ( pos , nn , gn )
local r = 0
for _ , v in pairs ( nn ) do
local p = vector.new ( v )
repeat
local n = minetest.get_node ( p ) . name
p = vector.offset ( p , 0 , - 1 , 0 )
until not n or n == " mcl_core:bedrock " or n == " ignore " or n == gn
--minetest.log(tostring(pos.y - p.y))
if pos.y - p.y > r then r = pos.y - p.y end
end
return r
end
local function get_foundation_nodes ( ground_p1 , ground_p2 , pos , sidelen , node_stone )
local replace = { " air " , " group:liquid " , " mcl_core:snow " , " group:tree " , " group:leaves " , " group:plant " , " grass_block " , " group:dirt " }
local depth = find_ground ( pos , minetest.find_nodes_in_area ( ground_p1 , ground_p2 , replace ) , node_stone )
local nn = smooth_cube ( minetest.find_nodes_in_area ( vector.offset ( ground_p1 , 0 , - 1 , 0 ) , vector.offset ( ground_p2 , 0 , - depth , 0 ) , replace ) , vector.offset ( pos , 0 , - depth , 0 ) , true , sidelen * 64 )
2022-08-01 01:43:23 +02:00
local stone = { }
local filler = { }
local top = { }
local dust = { }
for l , v in pairs ( nn ) do
if v.y == ground_p1.y - 1 then
table.insert ( filler , v )
table.insert ( top , vector.offset ( v , 0 , 1 , 0 ) )
table.insert ( dust , vector.offset ( v , 0 , 2 , 0 ) )
elseif v.y < ground_p1.y - 1 and v.y > ground_p2.y - 4 then table.insert ( filler , v )
elseif v.y < ground_p2.y - 3 and v.y > ground_p2.y - 5 then
if math.random ( 3 ) == 1 then
table.insert ( filler , v )
else
table.insert ( stone , v )
end
else
table.insert ( stone , v )
end
end
return stone , filler , top , dust
end
local function foundation ( ground_p1 , ground_p2 , pos , sidelen )
2024-06-14 12:01:03 +02:00
local node_stone = " blocks:stone "
2022-08-01 01:43:23 +02:00
local node_filler = " mcl_core:dirt "
local node_top = " mcl_core:dirt_with_grass " or minetest.get_node ( ground_p1 ) . name
local node_dust = nil
2022-10-29 00:43:15 +02:00
if mg_name ~= " v6 " then
2022-08-01 01:43:23 +02:00
local b = minetest.registered_biomes [ minetest.get_biome_name ( minetest.get_biome_data ( pos ) . biome ) ]
--minetest.log(dump(b.node_top))
if b then
if b.node_top then node_top = b.node_top end
if b.node_filler then node_filler = b.node_filler end
if b.node_stone then node_stone = b.node_stone end
if b.node_dust then node_dust = b.node_dust end
end
end
2022-08-10 15:14:51 +02:00
local stone , filler , top , dust = get_foundation_nodes ( ground_p1 , ground_p2 , pos , sidelen , node_stone )
minetest.bulk_set_node ( top , { name = node_top } , node_stone )
2022-08-01 01:43:23 +02:00
if node_dust then
minetest.bulk_set_node ( dust , { name = node_dust } )
end
minetest.bulk_set_node ( filler , { name = node_filler } )
minetest.bulk_set_node ( stone , { name = node_stone } )
end
2022-10-24 07:07:56 +02:00
local function process_queue ( )
if # place_queue < 1 then return end
local s = table.remove ( place_queue )
mcl_structures.place_schematic ( s.pos , s.file , s.rot , nil , true , " place_center_x,place_center_z " , function ( s )
if s.after_place then
s.after_place ( s.pos , s.def , s.pr )
end
end , s.pr )
minetest.after ( 0.5 , process_queue )
end
2022-11-05 00:55:17 +01:00
function mcl_structures . spawn_mobs ( mob , spawnon , p1 , p2 , pr , n , water )
2022-10-29 00:18:48 +02:00
n = n or 1
2022-11-05 00:55:17 +01:00
local sp = { }
if water then
local nn = minetest.find_nodes_in_area ( p1 , p2 , spawnon )
for k , v in pairs ( nn ) do
if minetest.get_item_group ( minetest.get_node ( vector.offset ( v , 0 , 1 , 0 ) ) . name , " water " ) > 0 then
table.insert ( sp , v )
end
end
else
sp = minetest.find_nodes_in_area_under_air ( p1 , p2 , spawnon )
end
2022-10-29 00:18:48 +02:00
table.shuffle ( sp )
for i , node in pairs ( sp ) do
2022-10-30 01:54:33 +02:00
if not peaceful and i <= n then
2022-10-29 00:18:48 +02:00
local pos = vector.offset ( node , 0 , 1 , 0 )
if pos then
minetest.add_entity ( pos , mob )
end
end
minetest.get_meta ( node ) : set_string ( " spawnblock " , " yes " )
end
end
2022-10-24 07:07:56 +02:00
2022-10-29 00:43:15 +02:00
function mcl_structures . place_structure ( pos , def , pr , blockseed , rot )
2022-06-10 04:34:58 +02:00
if not def then return end
2022-10-24 05:26:47 +02:00
if not rot then rot = " random " end
2022-09-13 01:14:02 +02:00
local log_enabled = logging and not def.terrain_feature
2022-06-10 04:34:58 +02:00
local y_offset = 0
2022-06-10 01:06:24 +02:00
if type ( def.y_offset ) == " function " then
y_offset = def.y_offset ( pr )
elseif def.y_offset then
y_offset = def.y_offset
end
2022-06-24 02:11:24 +02:00
local pp = vector.offset ( pos , 0 , y_offset , 0 )
2022-06-22 16:47:43 +02:00
if def.solid_ground and def.sidelen then
2022-06-23 20:22:36 +02:00
local ground_p1 = vector.offset ( pos , - def.sidelen / 2 , - 1 , - def.sidelen / 2 )
local ground_p2 = vector.offset ( pos , def.sidelen / 2 , - 1 , def.sidelen / 2 )
2022-06-24 21:47:44 +02:00
2022-06-23 20:22:36 +02:00
local solid = minetest.find_nodes_in_area ( ground_p1 , ground_p2 , { " group:solid " } )
2022-06-24 02:11:24 +02:00
if # solid < ( def.sidelen * def.sidelen ) then
2022-06-23 20:22:36 +02:00
if def.make_foundation then
2022-08-01 01:43:23 +02:00
foundation ( vector.offset ( pos , - def.sidelen / 2 - 3 , - 1 , - def.sidelen / 2 - 3 ) , vector.offset ( pos , def.sidelen / 2 + 3 , - 1 , def.sidelen / 2 + 3 ) , pos , def.sidelen )
2022-06-23 20:22:36 +02:00
else
2022-09-13 01:14:02 +02:00
if log_enabled then
2022-06-25 06:16:18 +02:00
minetest.log ( " warning " , " [mcl_structures] " .. def.name .. " at " .. minetest.pos_to_string ( pp ) .. " not placed. No solid ground. " )
2022-06-23 20:22:36 +02:00
end
return false
2022-06-22 16:47:43 +02:00
end
end
end
2022-07-02 03:44:13 +02:00
if def.on_place and not def.on_place ( pos , def , pr , blockseed ) then
2022-09-13 01:14:02 +02:00
if log_enabled then
2022-06-25 06:16:18 +02:00
minetest.log ( " warning " , " [mcl_structures] " .. def.name .. " at " .. minetest.pos_to_string ( pp ) .. " not placed. Conditions not satisfied. " )
2022-06-22 16:37:44 +02:00
end
2022-06-19 00:17:43 +02:00
return false
end
2022-06-10 01:06:24 +02:00
if def.filenames then
2022-06-24 04:10:21 +02:00
if # def.filenames <= 0 then return false end
local r = pr : next ( 1 , # def.filenames )
local file = def.filenames [ r ]
2022-06-24 02:11:24 +02:00
if file then
2022-10-24 07:07:56 +02:00
local rot = rotations [ pr : next ( 1 , # rotations ) ]
2022-07-02 03:44:13 +02:00
local ap = function ( pos , def , pr , blockseed ) end
2022-06-24 02:11:24 +02:00
2022-10-24 07:07:56 +02:00
if def.daughters then
2022-11-01 03:27:34 +01:00
ap = function ( pos , def , pr , blockseed )
for _ , d in pairs ( def.daughters ) do
local p = vector.add ( pos , d.pos )
local rot = d.rot or 0
mcl_structures.place_schematic ( p , d.files [ pr : next ( 1 , # d.files ) ] , rot , nil , true , " place_center_x,place_center_z " , function ( )
2022-10-24 07:07:56 +02:00
if def.loot then generate_loot ( pp , def , pr , blockseed ) end
if def.construct_nodes then construct_nodes ( pp , def , pr , blockseed ) end
if def.after_place then
def.after_place ( pos , def , pr )
end
end , pr )
end
end
2022-10-28 21:50:44 +02:00
elseif def.after_place then
ap = def.after_place
2022-10-24 07:07:56 +02:00
end
mcl_structures.place_schematic ( pp , file , rot , def.replacements , true , " place_center_x,place_center_z " , function ( p1 , p2 , size , rotation )
if not def.daughters then
if def.loot then generate_loot ( pp , def , pr , blockseed ) end
if def.construct_nodes then construct_nodes ( pp , def , pr , blockseed ) end
end
2022-10-28 21:50:44 +02:00
return ap ( pp , def , pr , blockseed , p1 , p2 , size , rotation )
2022-06-24 03:36:21 +02:00
end , pr )
2022-09-13 01:14:02 +02:00
if log_enabled then
2022-06-25 06:16:18 +02:00
minetest.log ( " action " , " [mcl_structures] " .. def.name .. " placed at " .. minetest.pos_to_string ( pp ) )
2022-06-24 02:11:24 +02:00
end
return true
2022-06-22 16:37:44 +02:00
end
2022-09-11 23:44:33 +02:00
elseif def.place_func and def.place_func ( pp , def , pr , blockseed ) then
if not def.after_place or ( def.after_place and def.after_place ( pp , def , pr , blockseed ) ) then
2022-09-11 22:41:36 +02:00
if def.loot then generate_loot ( pp , def , pr , blockseed ) end
if def.construct_nodes then construct_nodes ( pp , def , pr , blockseed ) end
2022-09-13 01:14:02 +02:00
if log_enabled then
2022-06-25 06:16:18 +02:00
minetest.log ( " action " , " [mcl_structures] " .. def.name .. " placed at " .. minetest.pos_to_string ( pp ) )
2022-06-22 16:37:44 +02:00
end
2022-06-10 04:34:58 +02:00
return true
end
2022-06-10 01:06:24 +02:00
end
2022-09-13 01:14:02 +02:00
if log_enabled then
2022-06-22 16:37:44 +02:00
minetest.log ( " warning " , " [mcl_structures] placing " .. def.name .. " failed at " .. minetest.pos_to_string ( pos ) )
end
2022-06-10 01:06:24 +02:00
end
2022-06-06 03:34:31 +02:00
function mcl_structures . register_structure ( name , def , nospawn ) --nospawn means it will be placed by another (non-nospawn) structure that contains it's structblock i.e. it will not be placed by mapgen directly
2022-08-09 19:28:30 +02:00
if mcl_structures.is_disabled ( name ) then return end
2022-06-06 03:34:31 +02:00
local structblock = " mcl_structures:structblock_ " .. name
local flags = " place_center_x, place_center_z, force_placement "
local y_offset = 0
2022-06-10 01:06:24 +02:00
local sbgroups = { structblock = 1 , not_in_creative_inventory = 1 }
2022-06-06 03:34:31 +02:00
if def.flags then flags = def.flags end
def.name = name
2022-06-10 01:06:24 +02:00
if nospawn then
sbgroups.structblock = nil
sbgroups.structblock_lbm = 1
else
2022-09-10 21:39:56 +02:00
if def.place_on then
minetest.register_on_mods_loaded ( function ( ) --make sure all previous decorations and biomes have been registered
def.deco = minetest.register_decoration ( {
name = " mcl_structures:deco_ " .. name ,
decoration = structblock ,
deco_type = " simple " ,
place_on = def.place_on ,
spawn_by = def.spawn_by ,
num_spawn_by = def.num_spawn_by ,
sidelen = 80 ,
fill_ratio = def.fill_ratio ,
noise_params = def.noise_params ,
flags = flags ,
biomes = def.biomes ,
y_max = def.y_max ,
y_min = def.y_min
} )
2022-09-13 02:37:48 +02:00
minetest.register_node ( " : " .. structblock , { drawtype = " airlike " , walkable = false , pointable = false , groups = sbgroups , sunlight_propagates = true , } )
2022-09-10 21:39:56 +02:00
def.structblock = structblock
def.deco_id = minetest.get_decoration_id ( " mcl_structures:deco_ " .. name )
minetest.set_gen_notify ( { decoration = true } , { def.deco_id } )
--catching of gennotify happens in mcl_mapgen_core
2022-09-11 23:44:33 +02:00
2022-09-10 21:39:56 +02:00
end )
end
2022-06-06 03:34:31 +02:00
end
2022-06-10 02:40:33 +02:00
mcl_structures.registered_structures [ name ] = def
2022-06-06 03:34:31 +02:00
end
2022-10-29 00:18:48 +02:00
local structure_spawns = { }
function mcl_structures . register_structure_spawn ( def )
2022-10-29 00:43:15 +02:00
--name,y_min,y_max,spawnon,biomes,chance,interval,limit
2022-10-29 00:18:48 +02:00
minetest.register_abm ( {
label = " Spawn " .. def.name ,
nodenames = def.spawnon ,
min_y = def.y_min or - 31000 ,
max_y = def.y_max or 31000 ,
2022-10-29 00:43:15 +02:00
interval = def.interval or 60 ,
chance = def.chance or 5 ,
2022-10-29 00:18:48 +02:00
action = function ( pos , node , active_object_count , active_object_count_wider )
2022-10-29 00:43:15 +02:00
local limit = def.limit or 7
2022-10-29 01:26:32 +02:00
if active_object_count_wider > limit + mob_cap_animal then return end
2022-10-29 00:43:15 +02:00
if active_object_count_wider > mob_cap_player then return end
2022-10-29 00:18:48 +02:00
local p = vector.offset ( pos , 0 , 1 , 0 )
if minetest.get_node ( p ) . name ~= " air " then return end
if minetest.get_meta ( pos ) : get_string ( " spawnblock " ) == " " then return end
if mg_name ~= " v6 " and mg_name ~= " singlenode " and def.biomes then
if table.indexof ( def.biomes , minetest.get_biome_name ( minetest.get_biome_data ( p ) . biome ) ) == - 1 then
return
end
end
local mobdef = minetest.registered_entities [ def.name ]
if mobdef.can_spawn and not mobdef.can_spawn ( p ) then return end
minetest.add_entity ( p , def.name )
end ,
} )
end
2022-06-10 01:06:24 +02:00
--lbm for secondary structures (structblock included in base structure)
2022-06-06 03:34:31 +02:00
minetest.register_lbm ( {
name = " mcl_structures:struct_lbm " ,
run_at_every_load = true ,
2022-06-10 01:06:24 +02:00
nodenames = { " group:structblock_lbm " } ,
2022-06-06 03:34:31 +02:00
action = function ( pos , node )
2022-06-24 02:11:24 +02:00
minetest.remove_node ( pos )
2022-06-06 03:34:31 +02:00
local name = node.name : gsub ( " mcl_structures:structblock_ " , " " )
2022-06-10 02:40:33 +02:00
local def = mcl_structures.registered_structures [ name ]
2022-06-10 01:06:24 +02:00
if not def then return end
2022-06-10 02:40:33 +02:00
mcl_structures.place_structure ( pos )
2022-06-06 03:34:31 +02:00
end
} )