minetest-mm/mods/rubiks/init.lua
2020-11-16 17:58:14 +01:00

472 lines
12 KiB
Lua

--Okay, so we're making a Rubik's Cube!
--Let's start with the basics.
local colors = {
'green', -- +Y
'blue', -- -Y
'red', -- +X
'orange', -- -X
'white', -- +Z
'yellow', -- -Z
}
local materials = {} --what you craft the spawner with
local tiles = {} --Base colors
local spawntex = {} --what is on the spawner
local cubetex = {} --what is on the cubelets
for color = 1, #colors do
materials[color] = 'wool:'..colors[color]
tiles[color] = 'wool_'..colors[color]..'.png'
spawntex[color] = tiles[color]..'^rubiks_three.png'
cubetex[color] = tiles[color]..'^rubiks_outline.png'
end
--is this the center of a face, on the edge, or is it a corner?
local function get_axesoff(pos)
axesoff = 0
dir = {x=0, y=0, z=0}
center = {unpack(pos)}
local meta = minetest.env:get_meta(pos)
local string = meta:get_string('cube_center')
if string ~= nil then
center = minetest.string_to_pos(string)
if center ~= nil then
dir = {x=pos.x-center.x, y=pos.y-center.y, z=pos.z-center.z}
axesoff = (dir.x ~= 0 and 1 or 0)
+ (dir.y ~= 0 and 1 or 0)
+ (dir.z ~= 0 and 1 or 0)
end
end
return axesoff, dir, center
end
--this isn't in the cubelets' on_construct
--because the meta already needs to be set
local function set_cubelet_formspec(pos, size)
axesoff, dir, center = get_axesoff(pos)
if axesoff == 1 then
local meta = minetest.env:get_meta(pos)
meta:set_string("formspec",
"size["..size..","..size.."]"..
"image_button_exit[0,0;1,1;"..minetest.inventorycube(
tiles[1]..'^rubiks_four.png',
tiles[6]..'^rubiks_four.png',
tiles[3]..'^rubiks_four.png')..
";larger;]"..
"image_button_exit[0,1;1,1;"..minetest.inventorycube(
spawntex[1],
spawntex[6],
spawntex[3])..
";reset;]"..
--"image_button_exit[0,2;1,1;rubiks_scramble.png;scramble;]"..
"image_button_exit[0,2;1,1;"..minetest.inventorycube(
tiles[1]..'^rubiks_two.png',
tiles[6]..'^rubiks_two.png',
tiles[3]..'^rubiks_two.png')..
";smaller;]"..
"image_button_exit[1,0;1,1;"..minetest.inventorycube(
spawntex[1],
spawntex[4],
spawntex[6])..
";L3;]"..
"image_button_exit[1,1;1,1;"..minetest.inventorycube(
spawntex[1],
tiles[6]..'^rubiks_with_orange.png^rubiks_three.png',
tiles[3]..'^rubiks_with_yellow.png^rubiks_three.png')..
";L1;]"..
"image_button_exit[1,2;1,1;"..minetest.inventorycube(
spawntex[1],
tiles[6]..'^rubiks_with_orange.png^rubiks_three.png^[transformR180',
tiles[3]..'^rubiks_with_yellow.png^rubiks_three.png^[transformR180')..
";L2;]"..
"image_button_exit[2,0;1,1;"..minetest.inventorycube(
spawntex[1],
spawntex[3],
spawntex[5])..
";R3;]"..
"image_button_exit[2,1;1,1;"..minetest.inventorycube(
spawntex[1],
tiles[6]..'^rubiks_with_red.png^rubiks_three.png',
tiles[3]..'^rubiks_with_white.png^rubiks_three.png')..
";R1;]"..
"image_button_exit[2,2;1,1;"..minetest.inventorycube(
spawntex[1],
tiles[6]..'^rubiks_with_red.png^rubiks_three.png^[transformR180',
tiles[3]..'^rubiks_with_white.png^rubiks_three.png^[transformR180')..
";R2;]"..
'')
end
end
local function expand_cube(pos, spawn)
for x = pos.x-1, pos.x+1 do
for y = pos.y-1, pos.y+1 do
for z = pos.z-1, pos.z+1 do
pos2 = {x=x, y=y, z=z}
if spawn then --create
--don't overwrite the spawner
if not(pos2.x==pos.x and pos2.y==pos.y and pos2.z==pos.z) then
--always starts the same direction
name = 'rubiks:cubelet'
minetest.env:add_node(pos2, {name = name})
--keep track of center for the purpose of rotating the cube
local meta = minetest.env:get_meta(pos2)
meta:set_string('cube_center',
minetest.pos_to_string(pos)
)
set_cubelet_formspec(pos2, 3)
end
else --delete
minetest.env:remove_node(pos2)
end
end
end
end
if create then
--keep a record so you can't get two cubes from one, or something like that
local meta = minetest.env:get_meta(pos)
meta:set_int('has_spawned', 1)
end
end
--can't make a rubik's cube without the cube
minetest.register_node('rubiks:cube', {
--spawner because I don't get the uv pos yet
description = "Rubik's Cube",
tiles = spawntex,
--show green, yellow, red sides to look 3d in inventory
inventory_image = minetest.inventorycube(spawntex[1], spawntex[6], spawntex[3]),
--want it to be diggable, quickly
groups = {crumbly=3},
on_punch = function(pos, node, puncher)
for x = pos.x-1, pos.x+1 do
for y = pos.y-1, pos.y+1 do
for z = pos.z-1, pos.z+1 do
if not(pos.x==x and pos.y==y and pos.z==z) then
if minetest.env:get_node({x=x, y=y, z=z}).name ~= 'air' then
--put it on a pedestal then remove the pedestal
minetest.chat_send_player(puncher:get_player_name(), "Clear some space for Rubik's cube to expand")
return
end
end
end
end
end
--surrounded by air, so
expand_cube(pos, true)
end,
can_dig = function(pos, digger)
--digging the center of a spawned cube yields
--an extra cube without this - don't cheat when flying
local meta = minetest.env:get_meta(pos)
if meta:get_int('has_spawned') == 1 then
return false
end
return true
end,
})
--100% wool, need a way to get wool now.
minetest.register_craft({
type = "shapeless",
output = "rubiks:cube",
recipe = materials,
})
local function rotate_cube(pos, dir, clockwise, layer)
--save cube to rotate without losing data
cube = {}
for x = -1, 1 do cube[x] = {}
for y = -1, 1 do cube[x][y] = {}
for z = -1, 1 do
--read absolute position, save relative position
pos2 = {x=pos.x+x, y=pos.y+y, z=pos.z+z}
cube[x][y][z] = {
node = minetest.env:get_node(pos2),
meta = minetest.env:get_meta(pos2):to_table()
}
end
end
end
--what side of the cube will be rotated on what axes
loadpos, axes = {0, 0, 0}, {}
if dir.x ~= 0 then
loadpos[1] = dir.x
for l=1, layer-1 do
loadpos[1] = loadpos[1] - dir.x
end
axes[1] = 3--z
axes[2] = 2--y
end
if dir.y ~= 0 then
loadpos[2] = dir.y
for l=1, layer-1 do
loadpos[2] = loadpos[2] - dir.y
end
axes[1] = 1--x
axes[2] = 3--z
end
if dir.z ~= 0 then
loadpos[3] = dir.z
for l=1, layer-1 do
loadpos[3] = loadpos[3] - dir.z
end
axes[1] = 2--y
axes[2] = 1--x
end
sign = true
if dir.x == -1 or dir.y == -1 or dir.z == -1 then
clockwise = not clockwise
--still clockwise, just from the opposite perspective
sign = false
end
--start rotating
for firstaxis = -1, 1 do loadpos[axes[1]] = firstaxis
for secondaxis = -1, 1 do loadpos[axes[2]] = secondaxis
--don't lose data here either
writepos = {unpack(loadpos)}
--rotate around center of face
writepos[axes[1]] = loadpos[axes[2]] * (clockwise and 1 or -1)
writepos[axes[2]] = loadpos[axes[1]] * (clockwise and -1 or 1)
--get absolute position
pos2 = {x=pos.x+writepos[1], y=pos.y+writepos[2], z=pos.z+writepos[3]}
--rotate cubelet itself
loadcubelet = cube[loadpos[1]][loadpos[2]][loadpos[3]]
name = loadcubelet.node.name
if name ~= 'rubiks:cube' then--continue end
--turnaxis = dir.x and 1 or dir.y and 2 or dir.z and 3
if dir.x ~= 0 then turnaxis = 1
elseif dir.y ~= 0 then turnaxis = 2
else turnaxis = 3 end
--print(minetest.registered_nodes['rubiks:cubelet'].tiles[getface(loadcubelet.node.param2, turnaxis, negative)])
--place it
minetest.env:add_node(pos2, {name = name, param2 =
axisRotate(loadcubelet.node.param2, turnaxis, clockwise and 90 or -90)
})
--
--print(colors[getface(loadcubelet.node.param2, turnaxis, sign)])
--
local meta = minetest.env:get_meta(pos2)
meta:from_table(loadcubelet.meta)
end
end
end
end
local function start_rotation(pos, clockwise, layer)
axesoff, dir, center = get_axesoff(pos)
if axesoff == 1 then --center
if layer == 6 then
for layer = 1, 3 do
rotate_cube(center, dir, clockwise, layer)
end
else
rotate_cube(center, dir, clockwise, layer)
end
elseif axesoff == 2 then --edge
else --corner
end
end
local function register_cubelets()
minetest.register_node('rubiks:cubelet', {
description = "Rubik's Cubelet",
tiles = cubetex,
inventory_image = minetest.inventorycube(cubetex[1], cubetex[6], cubetex[3]),
groups = {crumbly=2, not_in_creative_inventory = 1},
after_dig_node = function(pos, oldnode, oldmeta, digger)
local string = oldmeta.fields.cube_center
if string ~= nil then
pos = minetest.string_to_pos(string)
expand_cube(pos, false)
end
end,
drop = 'rubiks:cube',
on_punch = function(pos, node, puncher)
start_rotation(pos, true, 1)
end,
--cubelets not in the center of the face never get formspecs
on_receive_fields = function(pos, formname, fields, sender)
if fields.L1 then
start_rotation(pos, false, 1)
elseif fields.L2 then
start_rotation(pos, false, 3)
elseif fields.L3 then
start_rotation(pos, false, 6)
elseif fields.R1 then
start_rotation(pos, true, 1)
elseif fields.R2 then
start_rotation(pos, true, 3)
elseif fields.R3 then
start_rotation(pos, true, 6)
elseif fields.larger then
minetest.chat_send_player(sender:get_player_name(),
'TODO: make the cube have more layers'
)
elseif fields.smaller then
minetest.chat_send_player(sender:get_player_name(),
'TODO: make the cube have less layers'
)
else --reset
minetest.chat_send_player(sender:get_player_name(),
'TODO: toggle between reset/scramble'
)
end
end,
paramtype2 = 'facedir',
})
end register_cubelets()
--temporary aliases to update cleanly
for rotations = 1, 6 do
minetest.register_alias('rubiks:cubelet'..rotations, 'rubiks:cubelet')
end
--Stealable Code
--You may edit this for coding style
--Do not use this in your mod. This is for sharing only.
--Put this somewhere where all modders can get to it
-------------------------------------------------------------------------------
function axisRotate(facedir, turnaxis, turnrot)
turnrot = math.floor(turnrot / 90) % 4
axis = math.floor(facedir / 4)
rot = facedir % 4
if turnaxis == 1 then --x
if 3 == axis or axis == 4 then
if axis == 4 then turnrot = -turnrot end
rot = (rot + turnrot) % 4
else
for r = 0, turnrot-1 do
if axis == 0 then axis = 1
elseif axis == 1 then axis = 5
rot=(rot+2)%4
elseif axis == 5 then axis = 2
rot=(rot-2)%4
elseif axis == 2 then axis = 0
else
error("axisRotate: my bad")
end
end
end
elseif turnaxis == 2 then --y
if 0 == axis or axis == 5 then
if axis == 5 then turnrot = -turnrot end
rot = (rot + turnrot) % 4
else
for r = 0, turnrot-1 do
if axis == 1 then axis = 3
elseif axis == 3 then axis = 2
elseif axis == 2 then axis = 4
elseif axis == 4 then axis = 1
else
error("axisRotate: my bad")
end rot = (rot + 1) % 4
end
end
elseif turnaxis == 3 then --z
if 1 == axis or axis == 2 then
if axis == 2 then turnrot = -turnrot end
rot = (rot + turnrot) % 4
else
for r = 0, turnrot-1 do
if axis == 0 then axis = 4
elseif axis == 4 then axis = 5
elseif axis == 5 then axis = 3
elseif axis == 3 then axis = 0
else
error("axisRotate: my bad")
end
end
end
else
error("axisRotate: turnaxis not 1-3")
end
facedir = axis * 4 + rot
return facedir
end
local function rotfaces(faces, turnaxis, turnrot)
turnrot = turnrot % 4
for r = 0, turnrot-1 do
if turnaxis == 1 then --x
torot = {1, 5, 2, 6}
elseif turnaxis == 2 then --y
torot = {6, 4, 5, 3}
elseif turnaxis == 3 then --z
torot = {1, 4, 2, 3}
else
error("rotfaces: turnaxis: my bad")
end
wraparound = faces[torot[3]]
faces[torot[3]] = faces[torot[2]]
faces[torot[2]] = faces[torot[1]]
faces[torot[1]] = wraparound
end
return faces
end
function getfaces(facedir)
--FIXME?
--tiles ±Y±X±Z
--facedir axes +Y±Z±X-Y
axis = math.floor(facedir / 4)
rot = facedir % 4
-- +Y -Y +X -X +Z -Z
faces = {1, 2, 3, 4, 5, 6}
if axis == 0 then -- +Y
turnaxis = 2
elseif axis == 1 then -- +Z
faces = rotfaces(faces, 1, 1) -- +X
turnaxis = 3
elseif axis == 2 then -- -Z
faces = rotfaces(faces, 1, -1) -- -X
turnaxis = 3
rot = -rot
elseif axis == 3 then -- +X
faces = rotfaces(faces, 3, -1) -- -Z
turnaxis = 1
elseif axis == 4 then -- -X
faces = rotfaces(faces, 3, 1) -- +Z
turnaxis = 1
rot = -rot
elseif axis == 5 then -- -Y
faces = rotfaces(faces, 3, 2)-- ±Z
turnaxis = 2
rot = -rot
else
error("getfaces: bad facedir: "..facedir..' '..axis..' '..rot)
end
return rotfaces(faces, turnaxis, rot)
end
function getface(facedir, axis, sign)
faces = getfaces(facedir)
return faces[
axis == 1 and (sign and 3 or 4) or (
axis == 2 and (sign and 1 or 2) or (
axis == 3 and (sign and 5 or 6)
)
)
]
end