2024-08-23 10:55:30 +02:00
vl_structures.registered_structures = { }
local structure_boost = tonumber ( minetest.settings : get ( " vl_structures_boost " ) ) or 1
2024-09-05 01:03:33 +02:00
local logging = minetest.settings : get_bool ( " vl_structures_logging " , false )
local disabled_structures = minetest.settings : get ( " vl_structures_disabled " )
2024-08-23 10:55:30 +02:00
disabled_structures = disabled_structures and disabled_structures : split ( " , " ) or { }
2024-08-31 22:34:29 +02:00
function vl_structures . is_disabled ( structname )
2024-08-23 10:55:30 +02:00
return table.indexof ( disabled_structures , structname ) ~= - 1
end
2024-09-05 01:03:33 +02:00
local worldseed = minetest.get_mapgen_setting ( " seed " )
local RANDOM_SEED_OFFSET = 959 -- random constant that should be unique across each library
local vector_offset = vector.offset
2024-08-23 10:55:30 +02:00
--- Trim a full path name to its last two parts as short name for logging
local function basename ( filename )
local fn = string.split ( filename , " / " )
return # fn > 1 and ( fn [ # fn - 1 ] .. " / " .. fn [ # fn ] ) or fn [ # fn ]
end
--- Load a schematic file
-- @param filename string: file name
-- @param name string: for logging, optional
-- @return loaded schematic
function vl_structures . load_schematic ( filename , name )
-- load, and ensure we have size information
if filename == nil then error ( " Filename is nil for schematic " .. tostring ( name ) ) end
if type ( filename ) == " string " then minetest.log ( " action " , " Loading " .. filename ) end
local s = loadstring ( minetest.serialize_schematic ( filename , " lua " , { lua_use_comments = false , lua_num_indent_spaces = 0 } ) .. " return schematic " ) ( )
if not s then
minetest.log ( " warning " , " [vl_structures] failed to load schematic " .. basename ( filename ) )
return nil
elseif not s.size then
minetest.log ( " warning " , " [vl_structures] no size information for schematic " .. basename ( filename ) )
return nil
end
if logging then minetest.log ( " warning " , " [vl_structures] loaded schematic " .. basename ( filename ) .. " size " .. minetest.pos_to_string ( s.size ) ) end
if not s.name then s.name = name or basename ( filename ) end
return s
end
2024-08-24 19:24:31 +02:00
-- @param pos vector: Position
-- @param def table: containing
-- pos vector: position (center.x, base.y, center.z) -- flags NOT supported, resolve before!
-- size vector: structure size after rotation (!)
-- yoffset number: relative to base.y, typically <= 0
-- y_min number: minimum y range permitted
-- y_max number: maximum y range permitted
-- schematic string or schematic: as in minetest.place_schematic
-- rotation string: as in minetest.place_schematic
-- replacement table: as in minetest.place_schematic
-- force_placement boolean: as in minetest.place_schematic
-- prepare table: instructions for preparation (usually from definition)
-- tolerance number: tolerable ground unevenness, -1 to disable, default 10
-- foundation boolean or number: level ground underneath structure (true is a minimum depth of -3)
-- clear boolean: clear overhead area
-- clear_min number or string: height from base to start clearing, "top" to start at top
-- clear_max number: height from top to stop primary clearing
-- padding number: additional padding to increase the area, default 1
-- corners number: corner smoothing of foundation and clear, default 1
-- name string: for logging
-- place_func function: to call when placing the structure
-- @param pr PcgRandom: random generator
-- @param blockseed number: passed to place_func only
-- @param rot string: rotation
2024-08-23 10:55:30 +02:00
function vl_structures . place_structure ( pos , def , pr , blockseed , rot )
2024-08-24 19:24:31 +02:00
if not pos or not def then return end
2024-08-23 10:55:30 +02:00
local log_enabled = logging and not def.terrain_feature
-- load schematics the first time
if def.filenames and not def.schematics then
if # def.filenames == 0 then minetest.log ( " warning " , " [vl_structures] schematic " .. def.name .. " has an empty list of filenames. " ) end
def.schematics = { }
for _ , filename in ipairs ( def.filenames ) do
local s = vl_structures.load_schematic ( filename , def.name )
if s then table.insert ( def.schematics , s ) end
end
if def.daughters then
for _ , d in pairs ( def.daughters ) do
d.schematics = { }
for _ , filename in ipairs ( d.filenames ) do
local s = vl_structures.load_schematic ( filename , d.name )
if s then table.insert ( d.schematics , s ) end
end
end
end
end
-- Apply vertical offset for schematic
local yoffset = ( type ( def.y_offset ) == " function " and def.y_offset ( pr ) ) or def.y_offset or 0
if def.schematics and # def.schematics > 0 then
local schematic = def.schematics [ pr : next ( 1 , # def.schematics ) ]
rot = vl_structures.parse_rotation ( rot or " random " , pr )
2024-08-24 19:24:31 +02:00
vl_structures.place_schematic ( pos , yoffset , schematic , rot , def , pr )
2024-08-23 10:55:30 +02:00
if log_enabled then
minetest.log ( " verbose " , " [vl_structures] " .. def.name .. " to be placed at " .. minetest.pos_to_string ( pos ) )
end
return true
end
-- structure has a custom place function
if not def.place_func then
2024-09-05 01:03:33 +02:00
minetest.log ( " warning " , " [vl_structures] no schematics and no place_func for schematic " .. def.name )
2024-08-23 10:55:30 +02:00
return false
end
local pp = yoffset ~= 0 and vector_offset ( pos , 0 , yoffset , 0 ) or pos
if def.place_func and def.prepare then
2024-09-05 01:03:33 +02:00
minetest.log ( " warning " , " [vl_structures] needed prepare for " .. def.name .. " placed at " .. minetest.pos_to_string ( pp ) .. " but do not have size information. " )
2024-08-23 10:55:30 +02:00
end
if def.place_func and def.place_func ( pp , def , pr , blockseed ) then
2024-09-05 01:03:33 +02:00
if def.after_place and not def.after_place ( pos , def , pr , pmin , pmax , size , param.rotation ) then
minetest.log ( " warning " , " [vl_structures] after_place failed for structure " .. def.name )
2024-08-23 10:55:30 +02:00
return false
end
2024-09-05 01:03:33 +02:00
if log_enabled then
minetest.log ( " action " , " [vl_structures] " .. def.name .. " placed at " .. minetest.pos_to_string ( pp ) )
end
2024-09-05 23:13:08 +02:00
if def.name and not ( def.terrain_feature or def.no_registry ) then vl_structures.register_structures_spawn ( def.name , pos ) end
2024-09-05 01:03:33 +02:00
return true
2024-08-23 10:55:30 +02:00
elseif log_enabled then
2024-09-05 01:03:33 +02:00
if def.place_func then
minetest.log ( " warning " , " [vl_structures] place_func failed for structure " .. def.name )
else
minetest.log ( " warning " , " [vl_structures] do not know how to place structure " .. def.name )
end
2024-08-23 10:55:30 +02:00
end
end
2024-08-26 00:00:11 +02:00
-- local EMPTY_SCHEMATIC = { size = {x = 1, y = 1, z = 1}, data = { { name = "ignore" } } }
local EMPTY_SCHEMATIC = { size = { x = 0 , y = 0 , z = 0 } , data = { } }
2024-08-24 19:24:31 +02:00
--- Register a structure
-- @param name string: Structure name
-- @param def table: Structure definition
function vl_structures . register_structure ( name , def )
2024-08-23 10:55:30 +02:00
if vl_structures.is_disabled ( name ) then return end
def.name = name
vl_structures.registered_structures [ name ] = def
if def.prepare and def.prepare . clear == nil and ( def.prepare . clear_bottom or def.prepare . clear_top ) then def.prepare . clear = true end
2024-09-05 01:03:33 +02:00
if not def.fill_ratio and def.chunk_probability and not def.noise_params then
2024-08-23 10:55:30 +02:00
def.fill_ratio = 1.1 / 80 / 80 -- 1 per chunk, controlled by chunk probability only
end
2024-08-26 00:00:11 +02:00
def.flags = def.flags or vl_structures.DEFAULT_FLAGS
2024-08-23 10:55:30 +02:00
if def.filenames then
for _ , filename in ipairs ( def.filenames ) do
2024-09-05 01:03:33 +02:00
if mcl_util and not mcl_util.file_exists ( filename ) then
2024-08-23 10:55:30 +02:00
minetest.log ( " warning " , " [vl_structures] schematic " .. ( name or " unknown " ) .. " is missing file " .. basename ( filename ) )
return nil
end
end
end
if def.place_on then
minetest.register_on_mods_loaded ( function ( )
2024-09-05 01:03:33 +02:00
local register_decoration = mcl_mapgen_core.register_decoration or minetest.register_decoration -- optional dependency
register_decoration ( {
2024-08-26 00:00:11 +02:00
name = " vl_structures: " .. name ,
2024-08-23 10:55:30 +02:00
priority = def.priority or ( def.terrain_feature and 900 ) or 100 , -- run before regular decorations
2024-09-05 01:03:33 +02:00
fill_ratio = def.fill_ratio ,
noise_params = def.noise_params ,
y_max = def.y_max ,
y_min = def.y_min ,
biomes = def.biomes ,
2024-08-23 10:55:30 +02:00
place_on = def.place_on ,
spawn_by = def.spawn_by ,
num_spawn_by = def.num_spawn_by ,
sidelen = 80 , -- no def.sidelen subdivisions for now, this field was used differently before
2024-08-24 19:24:31 +02:00
flags = def.flags ,
2024-09-05 01:03:33 +02:00
deco_type = " schematic " ,
schematic = EMPTY_SCHEMATIC , -- use gennotify only
2024-08-26 00:00:11 +02:00
gen_callback = function ( t , minp , maxp , blockseed )
for _ , pos in ipairs ( t ) do
local pr = PcgRandom ( minetest.hash_node_position ( pos ) + worldseed + RANDOM_SEED_OFFSET )
if def.chunk_probability == nil or pr : next ( 0 , 1e9 ) * 1e-9 * def.chunk_probability <= structure_boost then
2024-10-20 21:00:12 +02:00
vl_structures.place_structure ( pos , def , pr , blockseed )
2024-08-26 00:00:11 +02:00
if def.chunk_probability ~= nil then break end -- allow only one per gennotify, e.g., on multiple surfaces
end
end
end
} )
2024-08-23 10:55:30 +02:00
end )
end
end
2024-09-05 23:13:08 +02:00
-- Persistent structure registry
local mod_storage = minetest.get_mod_storage ( )
local vl_structures_spawn_cache = { }
function vl_structures . register_structures_spawn ( name , pos )
if not name or not pos then return end
local data = vl_structures_spawn_cache [ name ]
if not data then
data = mod_storage : get ( " vl_structures:spawns: " .. name )
data = data and minetest.deserialize ( data ) or { }
end
table.insert ( data , pos )
mod_storage : set_string ( " vl_structures: " .. name , minetest.serialize ( data ) )
vl_structures_spawn_cache [ name ] = data
end
function vl_structures . get_structure_spawns ( name )
if name == nil then
local ret = { }
for k , _ in pairs ( vl_structures_spawn_cache ) do
table.insert ( ret , k )
end
return ret
end
local data = vl_structures_spawn_cache [ name ]
if not data then
data = mod_storage : get ( " vl_structures:spawns: " .. name )
if not data then return nil end
data = minetest.deserialize ( data )
vl_structures_spawn_cache [ name ] = data
end
return table.copy ( data )
end
2024-08-23 10:55:30 +02:00
-- To avoid a cyclic dependency, run this when modules have finished loading
2024-09-05 01:03:33 +02:00
-- Maybe we can eventually remove this - the end portal should likely go into the mapgen itself.
2024-08-23 10:55:30 +02:00
minetest.register_on_mods_loaded ( function ( )
2024-08-26 00:00:11 +02:00
mcl_mapgen_core.register_generator ( " static structures " , nil , function ( minp , maxp , blockseed )
2024-08-23 10:55:30 +02:00
for _ , struct in pairs ( vl_structures.registered_structures ) do
2024-08-26 00:00:11 +02:00
if struct.static_pos then
2024-08-24 19:24:31 +02:00
local pr -- initialize only when needed below
2024-08-23 10:55:30 +02:00
for _ , pos in pairs ( struct.static_pos ) do
if vector.in_area ( pos , minp , maxp ) then
pr = pr or PcgRandom ( worldseed + RANDOM_SEED_OFFSET )
vl_structures.place_structure ( pos , struct , pr , blockseed )
end
end
end
end
return false , false , false
2024-09-05 23:13:08 +02:00
end , 100 , true ) -- light in the end is sensitive to these options
2024-08-23 10:55:30 +02:00
end )