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>
cora 2022-06-15 03:14:12 +00:00
commit 20945db0e6
11 changed files with 437 additions and 68 deletions

@ -87,7 +87,7 @@ minetest.register_craftitem("mcl_end:ender_eye", {
end end
local origin = user:get_pos() local origin = user:get_pos()
origin.y = origin.y + 1.5 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 dim = mcl_worlds.pos_to_dimension(origin)
local is_creative = minetest.is_creative_enabled(user:get_player_name()) local is_creative = minetest.is_creative_enabled(user:get_player_name())

@ -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
})

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

@ -8,11 +8,11 @@ local pr = PseudoRandom(seed)
--schematics by chmodsayshello --schematics by chmodsayshello
local schems = { local schems = {
"shipwreck_full_damaged", modpath.."/schematics/".."shipwreck_full_damaged"..".mts",
"shipwreck_full_normal", modpath.."/schematics/".."shipwreck_full_normal"..".mts",
"shipwreck_full_back_damaged", modpath.."/schematics/".."shipwreck_full_back_damaged"..".mts",
"shipwreck_half_front", modpath.."/schematics/".."shipwreck_half_front"..".mts",
"shipwreck_half_back", modpath.."/schematics/".."shipwreck_half_back"..".mts",
} }
local function get_supply_loot() 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 = "TODO:bamboo", weight = 2, amount_min = 1, amount_max = 3 },
{ itemstring = "mcl_farming:pumpkin", 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 }, { itemstring = "mcl_tnt:tnt", weight = 1, amount_min = 1, amount_max = 2 },
} }
} }
end end
@ -75,61 +75,105 @@ local function fill_chests(p1,p2)
end end
end end
local function get_ocean_biomes() local ocean_biomes = {
local r = {} "RoofedForest_ocean",
for k,_ in pairs(minetest.registered_biomes) do "JungleEdgeM_ocean",
if k:find("_ocean") then table.insert(r,k) end "BirchForestM_ocean",
end "BirchForest_ocean",
return r "IcePlains_deep_ocean",
end "Jungle_deep_ocean",
local function get_beach_biomes() "Savanna_ocean",
local r = {} "MesaPlateauF_ocean",
for k,_ in pairs(minetest.registered_biomes) do "ExtremeHillsM_deep_ocean",
if k:find("_beach") or k:find("_shore") then table.insert(r,k) end "Savanna_deep_ocean",
end "SunflowerPlains_ocean",
return r "Swampland_deep_ocean",
end "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({ mcl_structures.register_structure("shipwreck",{
decoration = "mcl_shipwrecks:structblock",
deco_type = "simple",
place_on = {"group:sand","mcl_core:gravel"}, place_on = {"group:sand","mcl_core:gravel"},
spawn_by = {"group:water"}, spawn_by = {"group:water"},
num_spawn_by = 4, num_spawn_by = 4,
sidelen = 80, noise_params = {
fill_ratio = 0.00002, offset = 0,
flags = "place_center_x, place_center_z, force_placement", scale = 0.000022,
biomes = get_ocean_biomes(), spread = {x = 250, y = 250, z = 250},
y_max=water_level-4, seed = 3,
}) octaves = 3,
persist = 0.001,
--rare beached variant flags = "absvalue",
minetest.register_decoration({ },
decoration = "mcl_shipwrecks:structblock", flags = "force_placement",
deco_type = "simple", biomes = ocean_biomes,
place_on = {"group:sand","mcl_core:gravel","group:dirt"}, y_max = water_level-4,
spawn_by = {"group:water","air"}, y_min = mcl_vars.mg_overworld_min,
num_spawn_by = 4, filenames = schems,
sidelen = 80, y_offset = function(pr) return pr:next(-4,-2) end,
fill_ratio=0.000001, after_place = function(pos)
flags = "place_center_x, place_center_z, force_placement", fill_chests(vector.offset(pos,-20,-5,-20),vector.offset(pos,20,15,20))
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)
end end
}) })

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

@ -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)

@ -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
})

@ -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) mcl_structures.place_schematic(newpos, path, rotation or "random", nil, true, nil, temple_placement_callback, pr)
end end
local registered_structures = {} local structure_data = {}
--[[ Returns a table of structure of the specified type. --[[ Returns a table of structure of the specified type.
Currently the only valid parameter is "stronghold". 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. TODO: Implement this function for all other structure types as well.
]] ]]
function mcl_structures.get_registered_structures(structure_type) function mcl_structures.get_structure_data(structure_type)
if registered_structures[structure_type] then if structure_data[structure_type] then
return table.copy(registered_structures[structure_type]) return table.copy(structure_data[structure_type])
else else
return {} return {}
end end
end end
-- Register a structures table for the given type. The table format is the same as for -- Register a structures table for the given type. The table format is the same as for
-- mcl_structures.get_registered_structures. -- mcl_structures.get_structure_data.
function mcl_structures.register_structures(structure_type, structures) function mcl_structures.register_structure_data(structure_type, structures)
registered_structures[structure_type] = structures structure_data[structure_type] = structures
end end
local function dir_to_rotation(dir) local function dir_to_rotation(dir)
@ -571,6 +571,8 @@ local function dir_to_rotation(dir)
return "0" return "0"
end end
dofile(modpath.."/api.lua")
-- Debug command -- Debug command
minetest.register_chatcommand("spawnstruct", { 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", 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>”.") message = S("Error: No structure type given. Please use “/spawnstruct <type>”.")
errord = true errord = true
else 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>”.") message = S("Error: Unknown structure type. Please use “/spawnstruct <type>”.")
errord = true errord = true
end end
@ -628,3 +636,10 @@ minetest.register_chatcommand("spawnstruct", {
end end
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)

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

@ -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
})

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