VoxeLibre/mods/MAPGEN/vl_structures/api.lua

232 lines
9.8 KiB
Lua
Raw Normal View History

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")
disabled_structures = disabled_structures and disabled_structures:split(",") or {}
2024-08-31 22:34:29 +02:00
function vl_structures.is_disabled(structname)
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
--- 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
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
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)
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)
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.")
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)
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
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
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
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)
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
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
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
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,
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,
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
})
end)
end
end
-- 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
-- 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.
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)
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
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
end, 100, true) -- light in the end is sensitive to these options
end)