Add //maze3d command

This commit is contained in:
Starbeamrainbowlabs 2020-04-29 01:55:55 +01:00
parent 340191b2cf
commit a2d078e156
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
5 changed files with 270 additions and 8 deletions

View file

@ -15,6 +15,7 @@ If you can dream of it, it probably belongs here!
- [`//torus <major_radius> <minor_radius> <node_name>`](#torus-major_radius-minor_radius-node_name)
- [`//hollowtorus <major_radius> <minor_radius> <node_name>`](#hollowtorus-major_radius-minor_radius-node_name)
- [`//maze <replace_node> [<seed>]`](#maze-replace_node-seed)
- [`//maze3d <replace_node> [<seed>]`](#maze3d-replace_node-seed)
- [`//multi <command_a> <command_b> .....`](#multi-command_a-command_b-command_c-)
- [`//yy`](#yy)
- [`//nn`](#nn)
@ -77,13 +78,23 @@ Creates a hollow torus at position 1 with the radius major and minor radii. Work
### `//maze <replace_node> [<seed>]`
Generates a maze using replace_node as the walls and air as the paths. Uses [an algorithm of my own devising](https://starbeamrainbowlabs.com/blog/article.php?article=posts/070-Language-Review-Lua.html). It is guaranteed that you can get from every point to every other point in generated mazes, and there are no loops.
A seed can optionally be provided, which will cause the same maze to be generated every time (otherwise `os.time()` is used to dynamically set the seed).
Requires the currently selected area to be at least 3x3x3.
```
//maze ice
//maze stone 1234
```
### `//maze3d <replace_node> [<seed>]`
Same as `//maze`, but adapted for 3d - has all the same properties. Note that currently there's no way to adjust the height of the passageways generated (you'll need to scale the generated maze afterwards).
Requires the currently selected area to be at least 3x3 on the x and z axes.
```
//maze glass
//maze bush_leaves 12345
```
### `//multi <command_a> <command_b> <command_c> .....`
Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but does work with others too. Don't forget a space between commands!

View file

@ -13,3 +13,4 @@ dofile(minetest.get_modpath("worldeditadditions") .. "/overlay.lua")
dofile(minetest.get_modpath("worldeditadditions") .. "/ellipsoid.lua")
dofile(minetest.get_modpath("worldeditadditions") .. "/torus.lua")
dofile(minetest.get_modpath("worldeditadditions") .. "/maze.lua")
dofile(minetest.get_modpath("worldeditadditions") .. "/maze3d.lua")

View file

@ -114,8 +114,8 @@ function worldedit.maze(pos1, pos2, target_node, seed)
}
-- minetest.log("action", "extent: ("..extent.x..", "..extent.y..", "..extent.z..")")
if extent.x == 0 or extent.y == 0 or extent.z == 0 then
minetest.log("info", "[worldeditadditions/maze] error: either x, y, or z of the extent was zero")
if extent.x < 3 or extent.y < 3 or extent.z < 1 then
minetest.log("info", "[worldeditadditions/maze] error: either x, y of the extent were less than 3, or z of the extent was less than 1")
return 0
end

View file

@ -0,0 +1,202 @@
--- Generates a maze.
-- Algorithm origin: https://starbeamrainbowlabs.com/blog/article.php?article=posts/070-Language-Review-Lua.html
-- @module worldeditadditions.maze
----------------------------------
-- function to print out the world
----------------------------------
function printspace3d(space, w, h, d)
for z = 0, d - 1, 1 do
for y = 0, h - 1, 1 do
local line = ""
for x = 0, w - 1, 1 do
line = line .. space[z][y][x]
end
print(line)
end
print("")
end
end
-- Initialise the world
start_time = os.clock()
function generate_maze3d(seed, width, height, depth)
print("Generating maze "..width.."x"..height.."x"..depth)
math.randomseed(seed) -- seed the random number generator with the system clock
width = width - 1
height = height - 1
local world = {}
for z = 0, depth, 1 do
world[z] = {}
for y = 0, height, 1 do
world[z][y] = {}
for x = 0, width, 1 do
world[z][y][x] = "#"
end
end
end
-- do a random walk to create pathways
local nodes = {} -- the nodes left that we haven't investigated
local curnode = 1 -- the node we are currently operating on
local cx, cy, cz = 1, 1, 1 -- our current position
table.insert(nodes, { x = cx, y = cy, z = cz })
world[cz][cy][cx] = " "
while #nodes > 0 do
-- io.write("Nodes left: " .. curnode .. "\r")
--print("Nodes left: " .. #nodes)
local directions = "" -- the different directions we can move in
if cz - 2 > 0 and world[cz - 2][cy][cx] == "#" then
directions = directions .. "-"
-- print("cz: "..cz..", cz - 2: "..(cz-2)..", here: '"..world[cz][cy][cx].."', there: '"..world[cz - 2][cy][cx].."'")
end
if cz + 2 < depth and world[cz + 2][cy][cx] == "#" then
directions = directions .. "+"
end
if cy - 2 > 0 and world[cz][cy - 2][cx] == "#" then
directions = directions .. "u"
end
if cy + 2 < height and world[cz][cy + 2][cx] == "#" then
directions = directions .. "d"
end
if cx - 2 > 0 and world[cz][cy][cx - 2] == "#" then
directions = directions .. "l"
end
if cx + 2 < width and world[cz][cy][cx + 2] == "#" then
directions = directions .. "r"
end
print("Currently at ("..cx..", "..cy..", "..cz..") - directions: "..directions)
--print("radar output: '" .. directions .. "' (length: " .. #directions .. "), curnode: " .. curnode)
if #directions > 0 then
-- we still have somewhere that we can go
--print("This node is not a dead end yet.")
local curdirnum = math.random(1, #directions)
local curdir = string.sub(directions, curdirnum, curdirnum)
print("Picked direction '"..curdir.."' (index "..curdirnum..")")
if curdir == "+" then
world[cz + 1][cy][cx] = " "
world[cz + 2][cy][cx] = " "
cz = cz + 2
elseif curdir == "-" then
world[cz - 1][cy][cx] = " "
world[cz - 2][cy][cx] = " "
cz = cz - 2
elseif curdir == "u" then
world[cz][cy - 1][cx] = " "
world[cz][cy - 2][cx] = " "
cy = cy - 2
elseif curdir == "d" then
world[cz][cy + 1][cx] = " "
world[cz][cy + 2][cx] = " "
cy = cy + 2
elseif curdir == "l" then
world[cz][cy][cx - 1] = " "
world[cz][cy][cx - 2] = " "
cx = cx - 2
elseif curdir == "r" then
world[cz][cy][cx + 1] = " "
world[cz][cy][cx + 2] = " "
cx = cx + 2
end
print("Now at ("..cx..", "..cy..", "..cz..") ")
table.insert(nodes, { x = cx, y = cy, z = cz })
else
-- print("The node at " .. curnode .. " is a dead end.")
table.remove(nodes, curnode)
if #nodes > 0 then
--print("performing teleport.");
curnode = math.random(1, #nodes)
--print("New node: " .. curnode)
-- print("Nodes table: ")
-- print_r(nodes)
cx = nodes[curnode]["x"]
cy = nodes[curnode]["y"]
cz = nodes[curnode]["z"]
else
--print("Maze generation complete, no teleportation necessary.")
end
end
-- io.read("*l")
-- print("\n\n\n\n\n\n\n\n\n")
-- printspace(world, width + 1, height + 1, depth + 1)
end
return world
end
-- local world = maze(os.time(), width, height)
function worldedit.maze3d(pos1, pos2, target_node, seed)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
-- pos2 will always have the highest co-ordinates now
-- getExtent() returns the number of nodes in the VoxelArea, which might be larger than we actually asked for
local extent = {
x = (pos2.x - pos1.x) + 1,
y = (pos2.y - pos1.y) + 1,
z = (pos2.z - pos1.z) + 1
}
minetest.log("action", "extent: ("..extent.x..", "..extent.y..", "..extent.z..")")
if extent.x < 3 or extent.y < 3 or extent.z < 3 then
minetest.log("info", "[worldeditadditions/maze] error: either x, y, or z of the extent were less than 3")
return 0
end
-- Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data()
local node_id_air = minetest.get_content_id("air")
local node_id_target = minetest.get_content_id(target_node)
-- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1))
-- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2))
minetest.log("action", "Generating "..extent.x.."x"..extent.z.."x"..extent.z.." 3d maze from pos1 " .. worldeditadditions.vector.tostring(pos1).." to pos2 "..worldeditadditions.vector.tostring(pos2))
local maze = generate_maze3d(seed, extent.z, extent.y, extent.x) -- x & need to be the opposite way around to how we index it
printspace3d(maze, extent.z, extent.y, extent.x)
-- z y x is the preferred loop order, but that isn't really possible here
for z = pos2.z, pos1.z, - 1 do
for x = pos2.x, pos1.x, - 1 do
for y = pos2.y, pos1.y, - 1 do
local maze_x = (x - pos1.x) -- - 1
local maze_y = (y - pos1.y) -- - 1
local maze_z = (z - pos1.z) -- - 1
if maze_x < 0 then maze_x = 0 end
if maze_y < 0 then maze_y = 0 end
if maze_z < 0 then maze_z = 0 end
minetest.log("action", "x: "..x..", y: "..y..", z: "..z..", pos x: "..maze_x..", pos z: "..maze_z)
minetest.log("action", "value: "..maze[maze_x][maze_y][maze_z])
if maze[maze_x][maze_y][maze_z] == "#" then
data[area:index(x, y, z)] = node_id_target
else
data[area:index(x, y, z)] = node_id_air
end
end
end
end
-- Save the modified nodes back to disk & return
worldedit.manip_helpers.finish(manip, data)
return extent.x * extent.y * extent.z
end

View file

@ -322,7 +322,7 @@ end
minetest.register_chatcommand("/maze", {
params = "<replace_node> [<seed>]",
description = "Generates a maze covering the currently selected area with replace_node as the walls. Optionally takes a (integer) seed.",
description = "Generates a maze covering the currently selected area (must be at least 3x3 on the x,z axes) with replace_node as the walls. Optionally takes a (integer) seed.",
privs = { worldedit = true },
func = safe_region(function(name, params_text)
local replace_node, seed, has_seed = parse_params_maze(params_text)
@ -335,10 +335,7 @@ minetest.register_chatcommand("/maze", {
worldedit.player_notify(name, "Error: Invalid seed.")
return false
end
if not seed then
seed = os.time()
end
if not seed then seed = os.time() end
local start_time = os.clock()
local replaced = worldedit.maze(worldedit.pos1[name], worldedit.pos2[name], replace_node, seed)
@ -365,3 +362,54 @@ minetest.register_chatcommand("/maze", {
return (pos2.x - pos1.x) * (pos2.y - pos1.y) * (pos1.z - pos2.z)
end)
})
-- ███ ███ █████ ███████ ███████ ██████ ██████
-- ████ ████ ██ ██ ███ ██ ██ ██ ██
-- ██ ████ ██ ███████ ███ █████ █████ ██ ██
-- ██ ██ ██ ██ ██ ███ ██ ██ ██ ██
-- ██ ██ ██ ██ ███████ ███████ ██████ ██████
minetest.register_chatcommand("/maze3d", {
params = "<replace_node> [<seed>]",
description = "Generates a 3d maze covering the currently selected area (must be at least 3x3x3) with replace_node as the walls. Optionally takes a (integer) seed.",
privs = { worldedit = true },
func = safe_region(function(name, params_text)
local replace_node, seed, has_seed = parse_params_maze(params_text)
if not replace_node then
worldedit.player_notify(name, "Error: Invalid node name.")
return false
end
if not seed and has_seed then
worldedit.player_notify(name, "Error: Invalid seed.")
return false
end
if not seed then seed = os.time() end
local start_time = os.clock()
local replaced = worldedit.maze3d(worldedit.pos1[name], worldedit.pos2[name], replace_node, seed)
local time_taken = os.clock() - start_time
worldedit.player_notify(name, replaced .. " nodes replaced in " .. time_taken .. "s")
minetest.log("action", name .. " used //maze at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. replaced .. " nodes in " .. time_taken .. "s")
end, function(name, params_text)
local replace_node, seed, has_seed = parse_params_maze(params_text)
if not params_text then params_text = "" end
if not replace_node then
worldedit.player_notify(name, "Error: Invalid input '" .. params_text .. "' (specifically the replace node). Try '/help /maze3d' to learn how to use this command.")
return 0
end
if not seed and has_seed then
worldedit.player_notify(name, "Error: Invalid input '" .. params_text .. "' (specifically the seed). Try '/help /maze3d' to learn how to use this command.")
return 0
end
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
return (pos2.x - pos1.x) * (pos2.y - pos1.y) * (pos1.z - pos2.z)
end)
})