Add rotatable registry

- Add register_rotatable
  Registers a node that can be rotated.
- Add fix_rotatable_facedir
  Looks up equivalent facedir value from
  loopup array specified when registering.
- Replace rotate_node with rotate_facedir
  Rotates a facedir value instead of a node in-world.
- Add calculate_rotating_state
  To separate state-creation into its own function,
  and to fix an issue where HUD wouldn't update.
- Add nodecore_filtered_lookup
  Mimics NodeCore's rotation filtering to create
  a lookup table based on a passed-in function.
- Register lenses, prisms and panels
main
copygirl 1 year ago
parent f725074ac2
commit b379937511
  1. 113
      init.lua
  2. 2
      mod.conf
  3. 68
      rotate.lua

@ -1,47 +1,95 @@
local minetest, vector, nodecore, include
= minetest, vector, nodecore, include
local ipairs, minetest, vector, nodecore, include
= ipairs, minetest, vector, nodecore, include
local hud = include("hud")
local rotate = include("rotate")
local utility = include("utility")
-- TODO: Add crosshair indicators for rotating around edges.
-- TODO: Register only the nodes that should be rotatable.
-- TODO: Replace `on_rightclick` of intended nodes.
-- TODO: Replace `on_rightclick` of intended nodes?
-- TODO: Allow right-clicking while holding sneak with an empty hand to invert rotation.
-- TODO: Add some more comments.
-- Distance at which we want to rotate by "pushing" on an edge.
local EDGE_DISTANCE = 1.5 / 16 -- 1.5 texels (at 16² texture resolution)
-- Contains a per-player state that holds information, created by a raycast
-- done in `playerstep`, about the node this player might rotate. This is used
-- to decide what to do when a rightclick occurs and to update the their HUD.
local rotating_state = {}
nodecore.register_playerstep({
label = "extended rotating update",
action = function(player, player_data)
local name = player:get_player_name()
local state = {}; rotating_state[name] = state
local pointed_thing = player_data.raycast()
if (not pointed_thing) or pointed_thing.type ~= "node" then return nil end
state.pos = pointed_thing.under
state.node = minetest.get_node(state.pos)
if not rotate.is_rotatable(state.node) then return end
if vector.equals(pointed_thing.above, pointed_thing.under) then return end
state.face = pointed_thing.above - pointed_thing.under
local edge, distance = utility.find_closest_edge(state.node, pointed_thing)
if edge and distance > EDGE_DISTANCE then
state.axis = state.face
state.mode = "face"
else
state.axis = state.face:cross(edge):normalize():round()
state.mode = "edge"
-- Register rotatable nodes.
do
local function nodecore_filtered_lookup(eq_func)
local lookup = {}
for i = 0, 23 do
local facedir = nodecore.facedirs[i]
for j = 1, #lookup do
local other = nodecore.facedirs[lookup[j]]
if eq_func(facedir, other) then lookup[i] = j; break end
end
lookup[i] = lookup[i] or i
end
return lookup
end
local LENS_FILTERED_LOOKUP = nodecore_filtered_lookup(
function(a, b) return vector.equals(a.f, b.f) end)
local PRISM_FILTERED_LOOKUP = nodecore_filtered_lookup(
function(a, b) return vector.equals(a.f, b.r) and vector.equals(a.r, b.f) end)
local PANEL_FILTERED_LOOKUP = nodecore_filtered_lookup(
function(a, b) return vector.equals(a.f, b.r) and vector.equals(a.r, b.f) end)
for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do
rotate.register_rotatable("nc_optics:lens" .. lens_state, LENS_FILTERED_LOOKUP) end
for _, prism_state in ipairs({ "", "_on", "_gated" }) do
rotate.register_rotatable("nc_optics:prism" .. prism_state, PRISM_FILTERED_LOOKUP) end
rotate.register_rotatable("nc_doors:panel_plank" , PANEL_FILTERED_LOOKUP)
rotate.register_rotatable("nc_doors:panel_cobble", PANEL_FILTERED_LOOKUP)
end
-- Calculates the state for the specified `player` and player `data`.
-- This state contains information about how the player would rotate a block.
local function calculate_rotating_state(player, data)
local state = {}
local pointed_thing = data.raycast()
if (not pointed_thing) or pointed_thing.type ~= "node" then return end
state.pos = pointed_thing.under
state.node = minetest.get_node(state.pos)
if not rotate.is_rotatable(state.node) then return end
if vector.equals(pointed_thing.above, pointed_thing.under) then return end
state.face = pointed_thing.above - pointed_thing.under
local edge, distance = utility.find_closest_edge(state.node, pointed_thing)
-- FIXME: Something in `find_closest_edge` is causing an unexpected edge to be returned, hence the additional check.
if edge and distance <= EDGE_DISTANCE and state.face:multiply(edge):length() ~= 0 then
state.axis = state.face:cross(edge):normalize()
state.mode = "edge"
else
state.axis = state.face
state.mode = "face"
end
state.invert = player:get_player_control().sneak
hud.update_player_hud(player, state)
state.invert = player:get_player_control().sneak
local degrees = state.invert and 90 or -90
state.facedir = rotate.rotate_facedir(state.node.param2, state.axis, degrees)
state.facedir = rotate.fix_rotatable_facedir(state.node, state.facedir)
state.success = state.facedir and state.facedir ~= state.node.param2
return state
end
nodecore.register_playerstep({
label = "extended rotating update",
action = function(player, data)
local name = player:get_player_name()
rotating_state[name] = calculate_rotating_state(player, data)
hud.update_player_hud(player, rotating_state[name])
end,
})
@ -50,7 +98,6 @@ minetest.register_on_leaveplayer(function(player)
rotating_state[name] = nil
end)
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if not minetest.is_player(puncher) then return end
local name = puncher:get_player_name()
@ -61,6 +108,8 @@ minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if not vector.equals(pos, state.pos) then return end
if node.name ~= state.node.name or node.param2 ~= state.node.param2 then return end
local degrees = state.invert and 90 or -90
rotate.rotate_node(state.pos, state.node, state.axis, degrees)
-- Only continue if the node can actually be sucessfully rotated.
if not state.success then return end
minetest.swap_node(pos, nodecore.underride({ param2 = state.facedir }, node))
end)

@ -2,4 +2,4 @@ name = nc_extended_rotating
title = NodeCore Extended Rotating
author = copygirl
description = Rotate optics and doors from NodeCore with ease.
depends = nc_api_all
depends = nc_api_all, nc_optics, nc_doors

@ -7,6 +7,8 @@ local minetest, vector
local rotate = {}
local rotatable_registry = {}
-- Axis lookup table based on how Minetest's `facedir` operates.
local AXIS_LOOKUP = {
vector.new( 0, 1, 0), -- +Y
@ -50,14 +52,54 @@ for up_index, up in ipairs(AXIS_LOOKUP) do
end
end
local function register_rotatable(name, facedir_lookup)
local def = minetest.registered_nodes[name]
if not def then error("Unknown node '" .. name .. "'") end
if def.paramtype2 ~= "facedir" then error("Node '" .. name .. "' must be 'facedir'") end
rotatable_registry[name] = { lookup = facedir_lookup }
end
rotate.register_rotatable = register_rotatable
-- Returns whether the specified `node` is rotatable by this mod.
local function is_rotatable(node)
local name = node and node.name
local def = minetest.registered_nodes[name]
return def and def.paramtype2 == "facedir"
local name = node and node.name or ""
return not not rotatable_registry[name]
end
rotate.is_rotatable = is_rotatable
-- Fixes facedir for the specified `node` when rotated to `facedir`.
-- * Returns `nil` if the node can't rotate this way.
-- * Returns `facedir` when the node can rotate this way.
-- * Returns another facedir when the node should rotate another equivalent way.
local function fix_rotatable_facedir(node, facedir)
local name = node and node.name or ""
local entry = rotatable_registry[name]
if not entry then return nil end
if not entry.lookup then return facedir end
return entry.lookup[facedir]
end
rotate.fix_rotatable_facedir = fix_rotatable_facedir
-- Returns the specified `facedir` rotated around `axis` by `degrees` counter-clickwise.
local function rotate_facedir(facedir, axis, degrees)
if degrees % 90 ~= 0 then error("degrees must be divisible by 90") end
if axis_vector_to_index(axis) == nil then error("axis " .. tostring(axis) .. " is not an axis vector") end
local up = AXIS_LOOKUP[1 + math_floor(facedir / 4)]
local back = minetest.facedir_to_dir(facedir)
up = up :rotate_around_axis(axis, math_rad(degrees)):round()
back = back:rotate_around_axis(axis, math_rad(degrees)):round()
local up_index = axis_vector_to_index(up)
local back_index = axis_vector_to_index(back)
return FACEDIR_LOOKUP[up_index][back_index]
end
rotate.rotate_facedir = rotate_facedir
-- Rotates the specified default orientation `{ min, max }` box to
-- one matching Minetest's rotation algorithm pointing to `facedir`.
local function rotate_box_by_facedir(box, facedir)
if facedir == 0 then return end
@ -83,24 +125,4 @@ local function rotate_box_by_facedir(box, facedir)
end
rotate.rotate_box_by_facedir = rotate_box_by_facedir
-- Rotates `node` at the specified `pos` around `axis` by `degrees` counter-clockwise.
local function rotate_node(pos, node, axis, degrees)
if degrees % 90 ~= 0 then error("degrees must be divisible by 90") end
if axis_vector_to_index(axis) == nil then error("axis must be an axis vector") end
if not is_rotatable(node) then error("node is not rotatable") end
local up = AXIS_LOOKUP[1 + math_floor(node.param2 / 4)]
local back = minetest.facedir_to_dir(node.param2)
up = up :rotate_around_axis(axis, math_rad(degrees)):round()
back = back:rotate_around_axis(axis, math_rad(degrees)):round()
local up_index = axis_vector_to_index(up)
local back_index = axis_vector_to_index(back)
node.param2 = FACEDIR_LOOKUP[up_index][back_index]
minetest.set_node(pos, node)
end
rotate.rotate_node = rotate_node
return rotate

Loading…
Cancel
Save