Compare commits

...

2 Commits

Author SHA1 Message Date
copygirl b379937511 Add rotatable registry 7 months ago
copygirl f725074ac2 Forgot to link the links in README 7 months ago
  1. 4
      README.md
  2. 113
      init.lua
  3. 2
      mod.conf
  4. 68
      rotate.lua

@ -13,3 +13,7 @@ away from you. Sneaking inverts the direction of the rotation.
At this time, nodes that can be rotated are hardcoded into the mod, since
their valid orientations can't be easily extracted. A function mimicking
NodeCore's own rotation logic has to be provided.
[Minetest]: https://www.minetest.net/
[NodeCore]: https://content.minetest.net/packages/Warr1024/nodecore/
[Extended Placement]: https://content.minetest.net/packages/gamefreq0/extended_placement/

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