Compare commits

...

4 Commits

Author SHA1 Message Date
teknomunk 1e3f3ca172 Update mod.conf 2024-03-26 05:16:17 +00:00
teknomunk dd513b08df Move to mcl2_ namespace 2024-03-26 05:15:37 +00:00
teknomunk 860f2f1ff5 Add more rooms, add rules logic (currently broken) 2024-03-24 16:37:53 +00:00
teknomunk c379cf9efe Initial procedural structures code 2024-03-24 07:56:49 +00:00
52 changed files with 1188 additions and 0 deletions

View File

@ -0,0 +1,246 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local function debug(f)
minetest.log("warning",f)
end
local function is_occupied(state, pos)
return state.occupied[ tostring(pos) ]
end
local function set_occupied(state, pos)
state.occupied[ tostring(pos) ] = true
end
local function is_in_bounds(state, pos)
local struct = state.struct
if pos.x < 0 or pos.x >= struct.grid_limit.x then return false end
if pos.y < 0 or pos.y >= struct.grid_limit.y then return false end
if pos.z < 0 or pos.z >= struct.grid_limit.z then return false end
return true
end
local function rotate_rule(rule, rot)
if rot == 0 then
return rule
elseif rot == 2 then
return {
groups = rule.groups,
dir = -rule.dir,
pos = rule.pos
}
end
return rule
end
local function rotate_rules(rules, rot)
local new_rules = {}
for i,rule in ipairs(rules) do
new_rules[i] = rotate_rule(rule, rot)
end
return new_rules
end
local function does_rule_match(conditions, rule)
-- Rotate 180
local rotated = rotate_rule(conditions,2)
-- Make sure at least one group overlaps
if rotated.dir ~= rule.dir then return false end
for k,v in pairs(conditions.groups or{}) do
if rule.groups and rule.groups[k] == v then return true end
end
return false
end
local function add_node_pos(state, rule, new_grid_pos)
if not state:is_in_bounds(new_grid_pos) then
debug(tostring(new_grid_pos).." is out of bounds")
return
end
if state:is_occupied(new_grid_pos) then
debug(tostring(new_grid_pos).." is already occupied")
return
end
-- Check if the node is already in the list and add the rule if it is
for _,n in ipairs(state.nodes) do
if n.pos == new_grid_pos then
n.rules[#n.rules +1] = rule
return
end
end
-- Append the node
state.nodes[#state.nodes + 1] = { pos = new_grid_pos, rules = {rule}}
debug("adding "..tostring(new_grid_pos))
end
local function place_part(state, idx, rot, grid_pos)
local root_pos = state.root_pos
local struct = state.struct
local part = struct.parts[idx]
-- Make sure none of the spaces are occupied and all are in bounds
for x=1,part.size.x do
for y=1,part.size.y do
for z=1,part.size.z do
local pn = vector.offset(grid_pos, x-1, y-1, z-1)
if state:is_occupied(pn) then return end
if not state:is_in_bounds(pn) then return end
end
end
end
-- Place the part
debug("Placing part #"..tostring(idx).." ("..part.file..") at grid_pos="..tostring(grid_pos)..", root_pos="..tostring(root_pos))
local pos = root_pos + vector.new(
grid_pos.x * struct.grid_size.x,
grid_pos.y * struct.grid_size.y,
grid_pos.z * struct.grid_size.z
)
minetest.place_schematic(pos, modpath.."/schematics/".. struct.name .. "/" .. part.file, ({"0","90","180","270"})[rot], nil, false)
-- Run hook
if part.after_place then
part.after_place(state, pos)
end
-- Handle loot
if part.loot then
local p2 = pos + struct.grid_size
mcl_structures.fill_chests(pos, p2, part.loot or struct.loot, state.pr)
end
-- Set positions as occupied
for x=1,part.size.x do
for y=1,part.size.y do
for z=1,part.size.z do
local pn = vector.offset(grid_pos, x-1, y-1, z-1)
state:set_occupied(pn)
end
end
end
-- Add nodes to the state
for _,rule in ipairs(part.rules or {}) do
state:add_node_pos(rule, grid_pos + (rule.dir or vector.new(0,0,0)) + (rule.pos or vector.new(0,0,0)))
end
end
local function do_part_rules_contain(part_rules, rule)
for _,r in ipairs(part_rules) do
if does_rule_match(rule, r) then return true end
end
return false
end
local function do_part_rules_match(state, pid, rot, node)
local part_rules = state.struct.parts[pid].rules or {}
if node.rules then
for _,r in ipairs(node.rules) do
if not do_part_rules_contain(part_rules, r) then
debug("No match for rule")
debug(dump(r))
debug(dump(part_rules))
return false
end
end
end
return true
end
local function select_part(state, node)
local pr = state.pr
local struct = state.struct
local possible_parts = {}
local total_weight = 0
debug("node="..dump(node))
for i,part in ipairs(struct.parts) do
debug("checking "..part.file)
local valid_part = true
if part.can_use and not part.can_use(state) then
debug("can't use")
valid_part = false
end
if not do_part_rules_match(state, i, 0, node) then valid_part = false end
if valid_part then
debug("possible part: "..tostring(part.file))
possible_parts[#possible_parts+1] = {i=i,part=part}
total_weight = total_weight + (part.weight or 1)
end
end
if total_weight == 0 then return 0,0 end
local w = pr:next(1,total_weight)
for _,p in ipairs(possible_parts) do
if w <= (p.part.weight or 1) then
return p.i,0
end
w = w - (p.part.weight or 1)
end
return 0,0
end
function mcl_procedural_structures:place(name, root_pos, seed)
local state = {
root_pos = root_pos,
occupied = {},
nodes = {},
user = {},
-- functions
is_occupied = is_occupied,
set_occupied = set_occupied,
is_in_bounds = is_in_bounds,
place_part = place_part,
select_part = select_part,
add_node_pos = add_node_pos,
}
-- Get the structure information
local struct = self.structures[name]
if not struct then return end
state.struct = struct
-- Handle structure-specific setup
if struct.before_place then struct.before_place(state) end
-- Insert a default starting location if none was inserted by the structure
if #state.nodes == 0 then
state.nodes[1] = {pos = vector.new(0,0,0)}
end
-- Initialize random numbers from seed
local pr = PseudoRandom(seed)
state.pr = pr
local nodes = state.nodes
while #nodes > 0 do
debug("Nodes remaining: "..tostring(#nodes))
-- Pull out one node
local j = pr:next(1,#nodes)
local node = nodes[j]
if j ~= #nodes then
nodes[j] = nodes[#nodes]
end
nodes[#nodes] = nil
-- Place a schematic for this node if not already occupied
if not state:is_occupied(node) then
local id,rot = state:select_part(node)
state:place_part( id, rot, node.pos )
end
end
end

View File

@ -0,0 +1,26 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
mcl_procedural_structures = {
structures = {}
}
dofile(modpath.."/api.lua")
mcl_procedural_structures.structures.nether_fortress = dofile(modpath.."/nether_fortress.lua")
minetest.register_chatcommand("genstruct",{
params = "dungeon",
description = S("Generate a procedural structure near your position"),
privs = {debug = true},
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then return end
local pos = player:get_pos()
if not pos then return end
pos = vector.round(pos)
mcl_procedural_structures:place( "nether_fortress", vector.offset(pos,14,0,14), 1)
end
})

View File

@ -0,0 +1,4 @@
name = mcl2_procedural_structures
author = teknomunk
description = Procedurally generate structures from components
depends = mcl_init, mcl_structures, mcl_mobspawners

View File

@ -0,0 +1,318 @@
local BLAZE_SPAWNER_MAX_LIGHT = 11
loot_table = {
["mcl_chests:chest_small" ] ={
{
stacks_min = 1,
stacks_max = 2,
items = {
--{ itemstring = "FIXME:spectral_arrow", weight = 1, amount_min = 10, amount_max=28 },
{ itemstring = "mcl_blackstone:blackstone_gilded", weight = 1, amount_min = 8, amount_max=12 },
{ itemstring = "mcl_core:iron_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:gold_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:crying_obsidian", weight = 1, amount_min = 3, amount_max=8 },
{ itemstring = "mcl_bows:crossbow", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_core:goldblock", weight = 1, },
{ itemstring = "mcl_tools:sword_gold", weight = 1, },
{ itemstring = "mcl_tools:axe_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:helmet_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:chestplate_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:leggings_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:boots_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
}
},
{
stacks_min = 2,
stacks_max = 4,
items = {
{ itemstring = "mcl_bows:arrow", weight = 4, amount_min = 5, amount_max=17 },
{ itemstring = "mcl_mobitems:string", weight = 4, amount_min = 1, amount_max=6 },
{ itemstring = "mcl_core:iron_nugget", weight = 1, amount_min = 2, amount_max = 6 },
{ itemstring = "mcl_core:gold_nugget", weight = 1, amount_min = 2, amount_max = 6 },
{ itemstring = "mcl_mobitems:leather", weight = 1, amount_min = 1, amount_max = 3 },
}
},
{
stacks_min = 1,
stacks_max = 1,
items = {
{ itemstring = "mcl_compass:lodestone" },
{ itemstring = "mcl_armor:rib" },
}
}}
}
return {
name = "nether_fortress",
grid_size = vector.new(7,4,7),
grid_limit = vector.new(5,3,5),
loot = loot_table,
parts = {
-- never access this part
[0]={
file = "template_1x1x1.mts",
size = vector.new(1,1,1),
},
{
file = "corner_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 5,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 }, },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 }, },
},
},
{
file = "tee_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 8,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
},
},
{
file = "cross_glowstone_1x1x1.mts",
size = vector.new(1,1,1),
weight = 7,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "cross_hidden_1x1x1.mts",
size = vector.new(1,1,1),
weight = 2,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "cross_fake_1x1x1.mts",
size = vector.new(1,1,1),
weight = 6,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "corridor_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "corridor_2_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "corridor_3_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "quartz_lava_well_1x1x1.mts",
size = vector.new(1,1,1),
can_use = function(state)
return not state.user.has_lava_well
end,
after_place = function(state, pos)
state.user.has_lava_well = true
end,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
},
},
{
file = "hall_with_nether_wart_1x2x1.mts",
size = vector.new(2,1,1),
weight = 2,
rules = {
{ dir = vector.new( 1, 0, 0), pos = vector.new(1,0,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "lava_tunnel_1x2x1.mts",
size = vector.new(2,1,1),
weight = 2,
rules = {
{ dir = vector.new( 1, 0, 0), pos = vector.new(1,0,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } }
},
},
{
file = "courtyard_lava_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "stairs_1x1x2.mts",
size = vector.new(1,2,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0), groups = { wall=1 } },
},
},
{
file = "stairs_1x1x3.mts",
size = vector.new(1,3,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,2,0) },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,2,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,2,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,2,0), groups = { wall=1 } },
}
},
{
file = "mess_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
loot = {
["mcl_chests:chest_small"] = {{
stacks_min = 3,
stacks_max = 9,
items = {
{ itemstring = "mcl_mobitems:cooked_porkchop", weight = 10, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_mobitems:porkchop", weight = 10, amount_min = 1, amount_max = 30 },
{ itemstring = "mcl_mobitems:string", weight = 10, amount_min = 1, amount_max = 8 },
{ itemstring = "mcl_mobitems:rotten_flesh", weight = 20, amount_min = 5, amount_max = 16 },
{ itemstring = "mcl_core:gold_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_core:iron_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_mobitems:leather", weight = 15, amount_min = 1, amount_max = 4 },
}
}},
},
},
{
file = "mason_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
loot = {
["mcl_barrels:barrel_closed"] = {{
stacks_min = 3,
stacks_max = 12,
items = {
{ itemstring = "mcl_blackstone:blackstone_gilded", weight = 1, amount_min = 8, amount_max = 12 },
{ itemstring = "mcl_blackstone:blackstone", weight = 10, amount_min = 8, amount_max=12 },
{ itemstring = "mcl_core:gold_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_core:gold_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:goldblock", weight = 1, },
{ itemstring = "mcl_nether:quartz", weight = 5, amount_min = 1, amount_max = 15 },
}
}},
},
},
{
file = "spawner_1x1x2.mts",
size = vector.new(1,2,1),
rules = {
{ dir = vector.new(-1, 0, 0) },
{ dir = vector.new( 1, 0, 0) },
{ dir = vector.new( 0, 0, 1) },
{ dir = vector.new( 0, 0,-1) },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0) },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0) },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0) },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0) },
},
loot = loot_table,
can_use = function(state)
return (state.user.spawners or 0) < 2
end,
after_place = function(state, pos)
-- Track how many blaze spawners we have placed
state.user.spawners = ( state.user.spawnders or 0 ) + 1
--[[
local nodes = minetest.find_nodes_in_area(pos,vector.offset(pos,7,4*2+1,7),{"mcl_mobspawners:spawner"})
for _,p in ipairs(nodes) do
mcl_mobspawners.setup_spawner(p, "mobs_mc:blaze", 0, BLAZE_SPAWNER_MAX_LIGHT, 10, 8, 0)
end
--]]
end
},
},
}

View File

@ -0,0 +1,246 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local function debug(f)
minetest.log("warning",f)
end
local function is_occupied(state, pos)
return state.occupied[ tostring(pos) ]
end
local function set_occupied(state, pos)
state.occupied[ tostring(pos) ] = true
end
local function is_in_bounds(state, pos)
local struct = state.struct
if pos.x < 0 or pos.x >= struct.grid_limit.x then return false end
if pos.y < 0 or pos.y >= struct.grid_limit.y then return false end
if pos.z < 0 or pos.z >= struct.grid_limit.z then return false end
return true
end
local function rotate_rule(rule, rot)
if rot == 0 then
return rule
elseif rot == 2 then
return {
groups = rule.groups,
dir = -rule.dir,
pos = rule.pos
}
end
return rule
end
local function rotate_rules(rules, rot)
local new_rules = {}
for i,rule in ipairs(rules) do
new_rules[i] = rotate_rule(rule, rot)
end
return new_rules
end
local function does_rule_match(conditions, rule)
-- Rotate 180
local rotated = rotate_rule(conditions,2)
-- Make sure at least one group overlaps
if rotated.dir ~= rule.dir then return false end
for k,v in pairs(conditions.groups or{}) do
if rule.groups and rule.groups[k] == v then return true end
end
return false
end
local function add_node_pos(state, rule, new_grid_pos)
if not state:is_in_bounds(new_grid_pos) then
debug(tostring(new_grid_pos).." is out of bounds")
return
end
if state:is_occupied(new_grid_pos) then
debug(tostring(new_grid_pos).." is already occupied")
return
end
-- Check if the node is already in the list and add the rule if it is
for _,n in ipairs(state.nodes) do
if n.pos == new_grid_pos then
n.rules[#n.rules +1] = rule
return
end
end
-- Append the node
state.nodes[#state.nodes + 1] = { pos = new_grid_pos, rules = {rule}}
debug("adding "..tostring(new_grid_pos))
end
local function place_part(state, idx, rot, grid_pos)
local root_pos = state.root_pos
local struct = state.struct
local part = struct.parts[idx]
-- Make sure none of the spaces are occupied and all are in bounds
for x=1,part.size.x do
for y=1,part.size.y do
for z=1,part.size.z do
local pn = vector.offset(grid_pos, x-1, y-1, z-1)
if state:is_occupied(pn) then return end
if not state:is_in_bounds(pn) then return end
end
end
end
-- Place the part
debug("Placing part #"..tostring(idx).." ("..part.file..") at grid_pos="..tostring(grid_pos)..", root_pos="..tostring(root_pos))
local pos = root_pos + vector.new(
grid_pos.x * struct.grid_size.x,
grid_pos.y * struct.grid_size.y,
grid_pos.z * struct.grid_size.z
)
minetest.place_schematic(pos, modpath.."/schematics/".. struct.name .. "/" .. part.file, ({"0","90","180","270"})[rot], nil, false)
-- Run hook
if part.after_place then
part.after_place(state, pos)
end
-- Handle loot
if part.loot then
local p2 = pos + struct.grid_size
mcl_structures.fill_chests(pos, p2, part.loot or struct.loot, state.pr)
end
-- Set positions as occupied
for x=1,part.size.x do
for y=1,part.size.y do
for z=1,part.size.z do
local pn = vector.offset(grid_pos, x-1, y-1, z-1)
state:set_occupied(pn)
end
end
end
-- Add nodes to the state
for _,rule in ipairs(part.rules or {}) do
state:add_node_pos(rule, grid_pos + (rule.dir or vector.new(0,0,0)) + (rule.pos or vector.new(0,0,0)))
end
end
local function do_part_rules_contain(part_rules, rule)
for _,r in ipairs(part_rules) do
if does_rule_match(rule, r) then return true end
end
return false
end
local function do_part_rules_match(state, pid, rot, node)
local part_rules = state.struct.parts[pid].rules or {}
if node.rules then
for _,r in ipairs(node.rules) do
if not do_part_rules_contain(part_rules, r) then
debug("No match for rule")
debug(dump(r))
debug(dump(part_rules))
return false
end
end
end
return true
end
local function select_part(state, node)
local pr = state.pr
local struct = state.struct
local possible_parts = {}
local total_weight = 0
debug("node="..dump(node))
for i,part in ipairs(struct.parts) do
debug("checking "..part.file)
local valid_part = true
if part.can_use and not part.can_use(state) then
debug("can't use")
valid_part = false
end
if not do_part_rules_match(state, i, 0, node) then valid_part = false end
if valid_part then
debug("possible part: "..tostring(part.file))
possible_parts[#possible_parts+1] = {i=i,part=part}
total_weight = total_weight + (part.weight or 1)
end
end
if total_weight == 0 then return 0,0 end
local w = pr:next(1,total_weight)
for _,p in ipairs(possible_parts) do
if w <= (p.part.weight or 1) then
return p.i,0
end
w = w - (p.part.weight or 1)
end
return 0,0
end
function mcl_procedural_structures:place(name, root_pos, seed)
local state = {
root_pos = root_pos,
occupied = {},
nodes = {},
user = {},
-- functions
is_occupied = is_occupied,
set_occupied = set_occupied,
is_in_bounds = is_in_bounds,
place_part = place_part,
select_part = select_part,
add_node_pos = add_node_pos,
}
-- Get the structure information
local struct = self.structures[name]
if not struct then return end
state.struct = struct
-- Handle structure-specific setup
if struct.before_place then struct.before_place(state) end
-- Insert a default starting location if none was inserted by the structure
if #state.nodes == 0 then
state.nodes[1] = {pos = vector.new(0,0,0)}
end
-- Initialize random numbers from seed
local pr = PseudoRandom(seed)
state.pr = pr
local nodes = state.nodes
while #nodes > 0 do
debug("Nodes remaining: "..tostring(#nodes))
-- Pull out one node
local j = pr:next(1,#nodes)
local node = nodes[j]
if j ~= #nodes then
nodes[j] = nodes[#nodes]
end
nodes[#nodes] = nil
-- Place a schematic for this node if not already occupied
if not state:is_occupied(node) then
local id,rot = state:select_part(node)
state:place_part( id, rot, node.pos )
end
end
end

View File

@ -0,0 +1,26 @@
local modname = minetest.get_current_modname()
local S = minetest.get_translator(modname)
local modpath = minetest.get_modpath(modname)
mcl_procedural_structures = {
structures = {}
}
dofile(modpath.."/api.lua")
dofile(modpath.."/nether_fortress.lua")
minetest.register_chatcommand("genstruct",{
params = "dungeon",
description = S("Generate a procedural structure near your position"),
privs = {debug = true},
func = function(name, param)
local player = minetest.get_player_by_name(name)
if not player then return end
local pos = player:get_pos()
if not pos then return end
pos = vector.round(pos)
mcl_procedural_structures:place( "nether_fortress", vector.offset(pos,14,0,14), 1)
end
})

View File

@ -0,0 +1,4 @@
name = mcl_procedural_structures
author = teknomunk
description = Procedurally generate structures from components
depends = mcl_init, mcl_structures, mcl_mobspawners

View File

@ -0,0 +1,318 @@
local BLAZE_SPAWNER_MAX_LIGHT = 11
loot_table = {
["mcl_chests:chest_small" ] ={
{
stacks_min = 1,
stacks_max = 2,
items = {
--{ itemstring = "FIXME:spectral_arrow", weight = 1, amount_min = 10, amount_max=28 },
{ itemstring = "mcl_blackstone:blackstone_gilded", weight = 1, amount_min = 8, amount_max=12 },
{ itemstring = "mcl_core:iron_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:gold_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:crying_obsidian", weight = 1, amount_min = 3, amount_max=8 },
{ itemstring = "mcl_bows:crossbow", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_core:goldblock", weight = 1, },
{ itemstring = "mcl_tools:sword_gold", weight = 1, },
{ itemstring = "mcl_tools:axe_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:helmet_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:chestplate_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:leggings_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
{ itemstring = "mcl_armor:boots_gold", weight = 1, func = function(stack, pr)
mcl_enchanting.enchant_uniform_randomly(stack, {"soul_speed"}, pr)
end },
}
},
{
stacks_min = 2,
stacks_max = 4,
items = {
{ itemstring = "mcl_bows:arrow", weight = 4, amount_min = 5, amount_max=17 },
{ itemstring = "mcl_mobitems:string", weight = 4, amount_min = 1, amount_max=6 },
{ itemstring = "mcl_core:iron_nugget", weight = 1, amount_min = 2, amount_max = 6 },
{ itemstring = "mcl_core:gold_nugget", weight = 1, amount_min = 2, amount_max = 6 },
{ itemstring = "mcl_mobitems:leather", weight = 1, amount_min = 1, amount_max = 3 },
}
},
{
stacks_min = 1,
stacks_max = 1,
items = {
{ itemstring = "mcl_compass:lodestone" },
{ itemstring = "mcl_armor:rib" },
}
}}
}
mcl_procedural_structures.structures.nether_fortress = {
name = "nether_fortress",
grid_size = vector.new(7,4,7),
grid_limit = vector.new(5,3,5),
loot = loot_table,
parts = {
-- never access this part
[0]={
file = "template_1x1x1.mts",
size = vector.new(1,1,1),
},
{
file = "corner_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 5,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 }, },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 }, },
},
},
{
file = "tee_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 8,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
},
},
{
file = "cross_glowstone_1x1x1.mts",
size = vector.new(1,1,1),
weight = 7,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "cross_hidden_1x1x1.mts",
size = vector.new(1,1,1),
weight = 2,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "cross_fake_1x1x1.mts",
size = vector.new(1,1,1),
weight = 6,
rules = {
{ dir = vector.new( 0, 0,-1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "corridor_1_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "corridor_2_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "corridor_3_1x1x1.mts",
size = vector.new(1,1,1),
weight = 3,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "quartz_lava_well_1x1x1.mts",
size = vector.new(1,1,1),
can_use = function(state)
return not state.user.has_lava_well
end,
after_place = function(state, pos)
state.user.has_lava_well = true
end,
rules = {
{ dir = vector.new( 1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 0, 0, 1), groups = { corridor=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
},
},
{
file = "hall_with_nether_wart_1x2x1.mts",
size = vector.new(2,1,1),
weight = 2,
rules = {
{ dir = vector.new( 1, 0, 0), pos = vector.new(1,0,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
},
},
{
file = "lava_tunnel_1x2x1.mts",
size = vector.new(2,1,1),
weight = 2,
rules = {
{ dir = vector.new( 1, 0, 0), pos = vector.new(1,0,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } }
},
},
{
file = "courtyard_lava_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
},
{
file = "stairs_1x1x2.mts",
size = vector.new(1,2,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0), groups = { corridor=1 } },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0), groups = { wall=1 } },
},
},
{
file = "stairs_1x1x3.mts",
size = vector.new(1,3,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0), groups = { wall=1 } },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,2,0) },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,2,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,2,0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,2,0), groups = { wall=1 } },
}
},
{
file = "mess_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), groups = { corridor=1 } },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
loot = {
["mcl_chests:chest_small"] = {{
stacks_min = 3,
stacks_max = 9,
items = {
{ itemstring = "mcl_mobitems:cooked_porkchop", weight = 10, amount_min = 1, amount_max = 5 },
{ itemstring = "mcl_mobitems:porkchop", weight = 10, amount_min = 1, amount_max = 30 },
{ itemstring = "mcl_mobitems:string", weight = 10, amount_min = 1, amount_max = 8 },
{ itemstring = "mcl_mobitems:rotten_flesh", weight = 20, amount_min = 5, amount_max = 16 },
{ itemstring = "mcl_core:gold_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_core:iron_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_mobitems:leather", weight = 15, amount_min = 1, amount_max = 4 },
}
}},
},
},
{
file = "mason_1x1x1.mts",
size = vector.new(1,1,1),
rules = {
{ dir = vector.new(-1, 0, 0), },
{ dir = vector.new( 1, 0, 0), groups = { wall=1 } },
{ dir = vector.new( 0, 0,-1), groups = { wall=1 } },
{ dir = vector.new( 0, 0, 1), groups = { wall=1 } },
},
loot = {
["mcl_barrels:barrel_closed"] = {{
stacks_min = 3,
stacks_max = 12,
items = {
{ itemstring = "mcl_blackstone:blackstone_gilded", weight = 1, amount_min = 8, amount_max = 12 },
{ itemstring = "mcl_blackstone:blackstone", weight = 10, amount_min = 8, amount_max=12 },
{ itemstring = "mcl_core:gold_nugget", weight = 15, amount_min = 4, amount_max = 9 },
{ itemstring = "mcl_core:gold_ingot", weight = 1, amount_min = 4, amount_max=9 },
{ itemstring = "mcl_core:goldblock", weight = 1, },
{ itemstring = "mcl_nether:quartz", weight = 5, amount_min = 1, amount_max = 15 },
}
}},
},
},
{
file = "spawner_1x1x2.mts",
size = vector.new(1,2,1),
rules = {
{ dir = vector.new(-1, 0, 0) },
{ dir = vector.new( 1, 0, 0) },
{ dir = vector.new( 0, 0, 1) },
{ dir = vector.new( 0, 0,-1) },
{ dir = vector.new(-1, 0, 0), pos = vector.new(0,1,0) },
{ dir = vector.new( 1, 0, 0), pos = vector.new(0,1,0) },
{ dir = vector.new( 0, 0, 1), pos = vector.new(0,1,0) },
{ dir = vector.new( 0, 0,-1), pos = vector.new(0,1,0) },
},
loot = loot_table,
can_use = function(state)
return (state.user.spawners or 0) < 2
end,
after_place = function(state, pos)
-- Track how many blaze spawners we have placed
state.user.spawners = ( state.user.spawnders or 0 ) + 1
--[[
local nodes = minetest.find_nodes_in_area(pos,vector.offset(pos,7,4*2+1,7),{"mcl_mobspawners:spawner"})
for _,p in ipairs(nodes) do
mcl_mobspawners.setup_spawner(p, "mobs_mc:blaze", 0, BLAZE_SPAWNER_MAX_LIGHT, 10, 8, 0)
end
--]]
end
},
},
}