Merge pull request 'Structure placement api' (#2275) from structure_api into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/2275
Reviewed-by: MysticTempest <mystictempest@noreply.git.minetest.land>
This commit is contained in:
cora 2022-06-15 03:14:12 +00:00
commit 20945db0e6
11 changed files with 437 additions and 68 deletions

View File

@ -87,7 +87,7 @@ minetest.register_craftitem("mcl_end:ender_eye", {
end
local origin = user:get_pos()
origin.y = origin.y + 1.5
local strongholds = mcl_structures.get_registered_structures("stronghold")
local strongholds = mcl_structures.get_structure_data("stronghold")
local dim = mcl_worlds.pos_to_dimension(origin)
local is_creative = minetest.is_creative_enabled(user:get_player_name())

View File

@ -0,0 +1,88 @@
local adjacents = {
vector.new(1,0,0),
vector.new(-1,0,0),
vector.new(0,0,1),
vector.new(0,0,-1),
vector.new(0,1,0),
vector.new(0,-1,0)
}
local function set_node_no_bedrock(pos,node)
local n = minetest.get_node(pos)
if n.name == "mcl_core:bedrock" then return end
return minetest.set_node(pos,node)
end
local function makegeode(pos,pr)
local size = pr:next(4,7)
local p1 = vector.offset(pos,-size,-size,-size)
local p2 = vector.offset(pos,size,size,size)
local calcite = {}
local nn = minetest.find_nodes_in_area(p1,p2,{"group:material_stone"})
table.sort(nn,function(a, b)
return vector.distance(pos, a) < vector.distance(pos, b)
end)
if not nn[1] then return end
for i=1,math.random(#nn) do
set_node_no_bedrock(nn[i],{name="mcl_amethyst:amethyst_block"})
end
for k,v in pairs(minetest.find_nodes_in_area(p1,p2,{"mcl_amethyst:amethyst_block"})) do
local all_amethyst = true
for kk,vv in pairs(adjacents) do
local pp = vector.add(v,vv)
local an = minetest.get_node(pp)
if an.name ~= "mcl_amethyst:amethyst_block" then
if minetest.get_item_group(an.name,"material_stone") > 0 then
set_node_no_bedrock(pp,{name="mcl_amethyst:calcite"})
table.insert(calcite,pp)
if pr:next(1,5) == 1 then
set_node_no_bedrock(v,{name="mcl_amethyst:budding_amethyst_block"})
end
all_amethyst = false
elseif an.name ~= "mcl_amethyst:amethyst_block" and an.name ~= "air" then
all_amethyst = false
end
end
end
if all_amethyst then set_node_no_bedrock(v,{name="air"}) end
end
for _,v in pairs(calcite) do
for _,vv in pairs(minetest.find_nodes_in_area(vector.offset(v,-1,-1,-1),vector.offset(v,1,1,1),{"group:material_stone"})) do
set_node_no_bedrock(vv,{name="mcl_blackstone:basalt_smooth"})
end
end
for k,v in pairs(minetest.find_nodes_in_area_under_air(p1,p2,{"mcl_amethyst:amethyst_block","mcl_amethyst:budding_amethyst_block"})) do
local r = pr:next(1,50)
if r < 10 then
set_node_no_bedrock(vector.offset(v,0,1,0),{name="mcl_amethyst:amethyst_cluster",param2=1})
end
end
return true
end
mcl_structures.register_structure("geode",{
place_on = {"mcl_core:stone"},
noise_params = {
offset = 0,
scale = 0.00022,
spread = {x = 250, y = 250, z = 250},
seed = 7894353,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
flags = "place_center_x, place_center_z, force_placement",
biomes = ocean_biomes,
y_max = -24,
y_min = mcl_vars.mg_overworld_min,
filenames = schems,
y_offset = function(pr) return pr:next(-4,-2) end,
place_func = function(pos,def,pr)
local p = vector.new(pos.x + pr:next(-30,30),pos.y,pos.z + pr:next(-30,30))
return makegeode(p,pr)
end
})

View File

@ -0,0 +1,3 @@
name = mcl_geodes
author = cora
depends = mcl_init, mcl_structures

View File

@ -8,11 +8,11 @@ local pr = PseudoRandom(seed)
--schematics by chmodsayshello
local schems = {
"shipwreck_full_damaged",
"shipwreck_full_normal",
"shipwreck_full_back_damaged",
"shipwreck_half_front",
"shipwreck_half_back",
modpath.."/schematics/".."shipwreck_full_damaged"..".mts",
modpath.."/schematics/".."shipwreck_full_normal"..".mts",
modpath.."/schematics/".."shipwreck_full_back_damaged"..".mts",
modpath.."/schematics/".."shipwreck_half_front"..".mts",
modpath.."/schematics/".."shipwreck_half_back"..".mts",
}
local function get_supply_loot()
@ -41,7 +41,7 @@ local function get_supply_loot()
--{ itemstring = "TODO:bamboo", weight = 2, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_farming:pumpkin", weight = 2, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_tnt:tnt", weight = 1, amount_min = 1, amount_max = 2 },
}
}
end
@ -75,61 +75,105 @@ local function fill_chests(p1,p2)
end
end
local function get_ocean_biomes()
local r = {}
for k,_ in pairs(minetest.registered_biomes) do
if k:find("_ocean") then table.insert(r,k) end
end
return r
end
local function get_beach_biomes()
local r = {}
for k,_ in pairs(minetest.registered_biomes) do
if k:find("_beach") or k:find("_shore") then table.insert(r,k) end
end
return r
end
local ocean_biomes = {
"RoofedForest_ocean",
"JungleEdgeM_ocean",
"BirchForestM_ocean",
"BirchForest_ocean",
"IcePlains_deep_ocean",
"Jungle_deep_ocean",
"Savanna_ocean",
"MesaPlateauF_ocean",
"ExtremeHillsM_deep_ocean",
"Savanna_deep_ocean",
"SunflowerPlains_ocean",
"Swampland_deep_ocean",
"Swampland_ocean",
"MegaSpruceTaiga_deep_ocean",
"ExtremeHillsM_ocean",
"JungleEdgeM_deep_ocean",
"SunflowerPlains_deep_ocean",
"BirchForest_deep_ocean",
"IcePlainsSpikes_ocean",
"Mesa_ocean",
"StoneBeach_ocean",
"Plains_deep_ocean",
"JungleEdge_deep_ocean",
"SavannaM_deep_ocean",
"Desert_deep_ocean",
"Mesa_deep_ocean",
"ColdTaiga_deep_ocean",
"Plains_ocean",
"MesaPlateauFM_ocean",
"Forest_deep_ocean",
"JungleM_deep_ocean",
"FlowerForest_deep_ocean",
"MushroomIsland_ocean",
"MegaTaiga_ocean",
"StoneBeach_deep_ocean",
"IcePlainsSpikes_deep_ocean",
"ColdTaiga_ocean",
"SavannaM_ocean",
"MesaPlateauF_deep_ocean",
"MesaBryce_deep_ocean",
"ExtremeHills+_deep_ocean",
"ExtremeHills_ocean",
"MushroomIsland_deep_ocean",
"Forest_ocean",
"MegaTaiga_deep_ocean",
"JungleEdge_ocean",
"MesaBryce_ocean",
"MegaSpruceTaiga_ocean",
"ExtremeHills+_ocean",
"Jungle_ocean",
"RoofedForest_deep_ocean",
"IcePlains_ocean",
"FlowerForest_ocean",
"ExtremeHills_deep_ocean",
"MesaPlateauFM_deep_ocean",
"Desert_ocean",
"Taiga_ocean",
"BirchForestM_deep_ocean",
"Taiga_deep_ocean",
"JungleM_ocean"
}
minetest.register_node("mcl_shipwrecks:structblock", {drawtype="airlike", walkable = false, pointable = false,groups = {structblock=1,not_in_creative_inventory=1}})
local beach_biomes = {
"FlowerForest_beach",
"Forest_beach",
"StoneBeach",
"ColdTaiga_beach_water",
"Taiga_beach",
"Savanna_beach",
"Plains_beach",
"ExtremeHills_beach",
"ColdTaiga_beach",
"Swampland_shore",
"MushroomIslandShore",
"JungleM_shore",
"Jungle_shore"
}
minetest.register_decoration({
decoration = "mcl_shipwrecks:structblock",
deco_type = "simple",
mcl_structures.register_structure("shipwreck",{
place_on = {"group:sand","mcl_core:gravel"},
spawn_by = {"group:water"},
num_spawn_by = 4,
sidelen = 80,
fill_ratio = 0.00002,
flags = "place_center_x, place_center_z, force_placement",
biomes = get_ocean_biomes(),
y_max=water_level-4,
})
--rare beached variant
minetest.register_decoration({
decoration = "mcl_shipwrecks:structblock",
deco_type = "simple",
place_on = {"group:sand","mcl_core:gravel","group:dirt"},
spawn_by = {"group:water","air"},
num_spawn_by = 4,
sidelen = 80,
fill_ratio=0.000001,
flags = "place_center_x, place_center_z, force_placement",
biomes = get_beach_biomes(),
y_max = water_level + 4,
y_min = water_level - 1,
})
minetest.register_lbm({
name = "mcl_shipwrecks:struct_lbm",
run_at_every_load = true,
nodenames = {"mcl_shipwrecks:structblock"},
action = function(pos, node)
minetest.set_node(pos,{name="air"})
local file = modpath.."/schematics/"..schems[pr:next(1,#schems)]..".mts"
local pp = vector.offset(pos,0,pr:next(-4,-2),0)
mcl_structures.place_schematic(pp, file, "random", nil, true, "place_center_x,place_center_z", function()
fill_chests(vector.offset(pos,-20,-5,-20),vector.offset(pos,20,15,20))
end,pr)
noise_params = {
offset = 0,
scale = 0.000022,
spread = {x = 250, y = 250, z = 250},
seed = 3,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
flags = "force_placement",
biomes = ocean_biomes,
y_max = water_level-4,
y_min = mcl_vars.mg_overworld_min,
filenames = schems,
y_offset = function(pr) return pr:next(-4,-2) end,
after_place = function(pos)
fill_chests(vector.offset(pos,-20,-5,-20),vector.offset(pos,20,15,20))
end
})

View File

@ -61,7 +61,7 @@ local function init_strongholds()
end
end
mcl_structures.register_structures("stronghold", table.copy(strongholds))
mcl_structures.register_structure_data("stronghold", table.copy(strongholds))
strongholds_inited = true
end

View File

@ -0,0 +1,29 @@
# mcl_structures
Structure placement API for MCL2.
## mcl_structures.register_structure(name,structure definition,nospawn)
If nospawn is truthy the structure will not be placed by mapgen and the decoration parameters can be omitted. This is intended for secondary structures the placement of which gets triggered by the placement of other structures. It can also be used to register testing structures so they can be used with /spawnstruct.
### structure definition
{
fill_ratio = OR noise = {},
biomes = {},
y_min =,
y_max =,
place_on = {},
spawn_by = {},
num_spawn_by =
flags = (default: "place_center_x, place_center_z, force_placement")
(same as decoration def)
y_offset =, --can be a number or a function returning a number
filenames = {} OR place_func = function(pos,def,pr)
-- filenames can be a list of any schematics accepted by mcl_structures.place_schematic / minetest.place_schematic
after_place = function(pos,def,pr)
}
## mcl_structures.registered_structures
Table of the registered structure defintions indexed by name.
## mcl_structures.place_structure(pos, def, pr)
Places a structure using the mapgen placement function
## mcl_structures.place_schematic(pos, schematic, rotation, replacements, force_placement, flags, after_placement_callback, pr, callback_param)

View File

@ -0,0 +1,81 @@
mcl_structures.registered_structures = {}
function mcl_structures.place_structure(pos, def, pr)
if not def then return end
local y_offset = 0
if type(def.y_offset) == "function" then
y_offset = def.y_offset(pr)
elseif def.y_offset then
y_offset = def.y_offset
end
if def.filenames then
local file = def.filenames[pr:next(1,#def.filenames)]
local pp = vector.offset(pos,0,y_offset,0)
mcl_structures.place_schematic(pp, file, "random", nil, true, "place_center_x,place_center_z",def.after_place,pr,{pos,def})
minetest.log("action","[mcl_structures] "..def.name.." placed at "..minetest.pos_to_string(pos))
return true
elseif def.place_func and def.place_func(pos,def,pr) then
if not def.after_place or ( def.after_place and def.after_place(pos,def,pr) ) then
minetest.log("action","[mcl_structures] "..def.name.." placed at "..minetest.pos_to_string(pos))
return true
end
end
minetest.log("warning","[mcl_structures] placing "..def.name.." failed at "..minetest.pos_to_string(pos))
end
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
local structblock = "mcl_structures:structblock_"..name
local flags = "place_center_x, place_center_z, force_placement"
local y_offset = 0
local sbgroups = { structblock = 1, not_in_creative_inventory=1 }
if def.flags then flags = def.flags end
def.name = name
if nospawn then
sbgroups.structblock = nil
sbgroups.structblock_lbm = 1
else
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
})
local deco_id = minetest.get_decoration_id("mcl_structures:deco_"..name)
minetest.set_gen_notify({decoration=true}, { deco_id })
minetest.register_on_generated(function(minp, maxp, blockseed)
local gennotify = minetest.get_mapgen_object("gennotify")
local pr = PseudoRandom(blockseed + 42)
for _, pos in pairs(gennotify["decoration#"..deco_id] or {}) do
local realpos = vector.offset(pos,0,-1,0)
minetest.remove_node(realpos)
mcl_structures.place_structure(realpos,def,pr)
end
end)
end
minetest.register_node(":"..structblock, {drawtype="airlike", walkable = false, pointable = false,groups = sbgroups})
def.structblock = structblock
mcl_structures.registered_structures[name] = def
end
--lbm for secondary structures (structblock included in base structure)
minetest.register_lbm({
name = "mcl_structures:struct_lbm",
run_at_every_load = true,
nodenames = {"group:structblock_lbm"},
action = function(pos, node)
local name = node.name:gsub("mcl_structures:structblock_","")
local def = mcl_structures.registered_structures[name]
if not def then return end
minetest.remove_node(pos)
mcl_structures.place_structure(pos)
end
})

View File

@ -530,7 +530,7 @@ function mcl_structures.generate_desert_temple(pos, rotation, pr)
mcl_structures.place_schematic(newpos, path, rotation or "random", nil, true, nil, temple_placement_callback, pr)
end
local registered_structures = {}
local structure_data = {}
--[[ Returns a table of structure of the specified type.
Currently the only valid parameter is "stronghold".
@ -543,18 +543,18 @@ Format of return value:
TODO: Implement this function for all other structure types as well.
]]
function mcl_structures.get_registered_structures(structure_type)
if registered_structures[structure_type] then
return table.copy(registered_structures[structure_type])
function mcl_structures.get_structure_data(structure_type)
if structure_data[structure_type] then
return table.copy(structure_data[structure_type])
else
return {}
end
end
-- Register a structures table for the given type. The table format is the same as for
-- mcl_structures.get_registered_structures.
function mcl_structures.register_structures(structure_type, structures)
registered_structures[structure_type] = structures
-- mcl_structures.get_structure_data.
function mcl_structures.register_structure_data(structure_type, structures)
structure_data[structure_type] = structures
end
local function dir_to_rotation(dir)
@ -571,6 +571,8 @@ local function dir_to_rotation(dir)
return "0"
end
dofile(modpath.."/api.lua")
-- Debug command
minetest.register_chatcommand("spawnstruct", {
params = "desert_temple | desert_well | igloo | witch_hut | boulder | ice_spike_small | ice_spike_large | fossil | end_exit_portal | end_exit_portal_open | end_gateway_portal | end_portal_shrine | nether_portal | dungeon",
@ -619,6 +621,12 @@ minetest.register_chatcommand("spawnstruct", {
message = S("Error: No structure type given. Please use “/spawnstruct <type>”.")
errord = true
else
for n,d in pairs(mcl_structures.registered_structures) do
if n == param then
mcl_structures.place_structure(pos,d,pr)
return true,message
end
end
message = S("Error: Unknown structure type. Please use “/spawnstruct <type>”.")
errord = true
end
@ -628,3 +636,10 @@ minetest.register_chatcommand("spawnstruct", {
end
end
})
minetest.register_on_mods_loaded(function()
local p = ""
for n,_ in pairs(mcl_structures.registered_structures) do
p = p .. " | "..n
end
minetest.registered_chatcommands["spawnstruct"].params = minetest.registered_chatcommands["spawnstruct"].params .. p
end)

View File

@ -1,4 +1,4 @@
name = mcl_structures
author = Wuzzy
description = Structures for MCL2
author = Wuzzy, cora
description = Structure placement for MCL2
depends = mcl_loot

View File

@ -0,0 +1,106 @@
local adjacents = {
vector.new(1,0,0),
vector.new(1,0,1),
vector.new(1,0,-1),
vector.new(-1,0,0),
vector.new(-1,0,1),
vector.new(-1,0,-1),
vector.new(0,0,1),
vector.new(0,0,-1),
vector.new(0,-1,0)
}
local function set_node_no_bedrock(pos,node)
local n = minetest.get_node(pos)
if n.name == "mcl_core:bedrock" then return end
return minetest.set_node(pos,node)
end
local function airtower(pos)
for i=0,55 do
set_node_no_bedrock(vector.offset(pos,0,i,0),{name="air"})
end
end
local function makelake(pos,size,liquid,border,pr)
local node_under = minetest.get_node(vector.offset(pos,0,1,0))
local p1 = vector.offset(pos,-size,-size,-size)
local p2 = vector.offset(pos,size,size,size)
local nn = minetest.find_nodes_in_area(p1,p2,{"group:material_stone", "group:sand", "group:dirt"})
table.sort(nn,function(a, b)
return vector.distance(pos, a) < vector.distance(pos, b)
end)
if not nn[1] then return end
local y = pos.y + 1
local lq = {}
for i=1,pr:next(1,#nn) do
if nn[i].y == y then
set_node_no_bedrock(nn[i],{name=liquid})
airtower(vector.offset(nn[i],0,1,0))
table.insert(lq,nn[i])
end
end
for k,v in pairs(lq) do
for kk,vv in pairs(adjacents) do
local pp = vector.add(v,vv)
local an = minetest.get_node(pp)
local un = minetest.get_node(vector.offset(pp,0,1,0))
if not border then
if minetest.get_item_group(an.name,"solid") > 0 then
border = an.name
elseif minetest.get_item_group(minetest.get_node(nn[1]).name,"solid") > 0 then
border = minetest.get_node(nn[1]).name
else
border = "mcl_core:stone"
end
if border == "mcl_core:dirt" then border = "mcl_core:dirt_with_grass" end
end
if an.name ~= liquid then
set_node_no_bedrock(pp,{name=border})
if un.name ~= liquid then
airtower(vector.offset(pp,0,1,0))
end
end
end
end
return true
end
mcl_structures.register_structure("lavapool",{
place_on = {"group:sand", "group:dirt", "group:stone"},
noise_params = {
offset = 0,
scale = 0.0000022,
spread = {x = 250, y = 250, z = 250},
seed = 78375213,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
flags = "place_center_x, place_center_z, force_placement",
y_max = mcl_vars.mg_overworld_max,
y_min = minetest.get_mapgen_setting("water_level"),
place_func = function(pos,def,pr)
return makelake(pos,5,"mcl_core:lava_source","mcl_core:stone",pr)
end
})
mcl_structures.register_structure("water_lake",{
place_on = {"group:dirt","group:stone"},
noise_params = {
offset = 0,
scale = 0.000032,
spread = {x = 250, y = 250, z = 250},
seed = 734341353,
octaves = 3,
persist = 0.001,
flags = "absvalue",
},
flags = "place_center_x, place_center_z, force_placement",
y_max = mcl_vars.mg_overworld_max,
y_min = minetest.get_mapgen_setting("water_level"),
place_func = function(pos,def,pr)
return makelake(pos,5,"mcl_core:water_source",nil,pr)
end
})

View File

@ -0,0 +1,3 @@
name = mcl_surface_pools
author = cora
depends = mcl_init, mcl_structures