Compare commits

..

2 Commits

Author SHA1 Message Date
copygirl b379937511 Add rotatable registry 1 year ago
copygirl f725074ac2 Forgot to link the links in README 1 year ago
  1. 4
      README.md
  2. 87
      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 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 their valid orientations can't be easily extracted. A function mimicking
NodeCore's own rotation logic has to be provided. 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,28 +1,61 @@
local minetest, vector, nodecore, include local ipairs, minetest, vector, nodecore, include
= minetest, vector, nodecore, include = ipairs, minetest, vector, nodecore, include
local hud = include("hud") local hud = include("hud")
local rotate = include("rotate") local rotate = include("rotate")
local utility = include("utility") local utility = include("utility")
-- TODO: Add crosshair indicators for rotating around edges. -- 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. -- TODO: Add some more comments.
-- Distance at which we want to rotate by "pushing" on an edge. -- 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) 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 = {} local rotating_state = {}
nodecore.register_playerstep({ -- Register rotatable nodes.
label = "extended rotating update", do
action = function(player, player_data) local function nodecore_filtered_lookup(eq_func)
local name = player:get_player_name() local lookup = {}
local state = {}; rotating_state[name] = state 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)
local pointed_thing = player_data.raycast() for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do
if (not pointed_thing) or pointed_thing.type ~= "node" then return nil end 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.pos = pointed_thing.under
state.node = minetest.get_node(state.pos) state.node = minetest.get_node(state.pos)
@ -32,16 +65,31 @@ nodecore.register_playerstep({
state.face = pointed_thing.above - pointed_thing.under state.face = pointed_thing.above - pointed_thing.under
local edge, distance = utility.find_closest_edge(state.node, pointed_thing) local edge, distance = utility.find_closest_edge(state.node, pointed_thing)
if edge and distance > EDGE_DISTANCE then -- 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.axis = state.face
state.mode = "face" state.mode = "face"
else
state.axis = state.face:cross(edge):normalize():round()
state.mode = "edge"
end end
state.invert = player:get_player_control().sneak state.invert = player:get_player_control().sneak
hud.update_player_hud(player, state)
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, end,
}) })
@ -50,7 +98,6 @@ minetest.register_on_leaveplayer(function(player)
rotating_state[name] = nil rotating_state[name] = nil
end) end)
minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing) minetest.register_on_punchnode(function(pos, node, puncher, pointed_thing)
if not minetest.is_player(puncher) then return end if not minetest.is_player(puncher) then return end
local name = puncher:get_player_name() 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 not vector.equals(pos, state.pos) then return end
if node.name ~= state.node.name or node.param2 ~= state.node.param2 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 -- Only continue if the node can actually be sucessfully rotated.
rotate.rotate_node(state.pos, state.node, state.axis, degrees) if not state.success then return end
minetest.swap_node(pos, nodecore.underride({ param2 = state.facedir }, node))
end) end)

@ -2,4 +2,4 @@ name = nc_extended_rotating
title = NodeCore Extended Rotating title = NodeCore Extended Rotating
author = copygirl author = copygirl
description = Rotate optics and doors from NodeCore with ease. 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 rotate = {}
local rotatable_registry = {}
-- Axis lookup table based on how Minetest's `facedir` operates. -- Axis lookup table based on how Minetest's `facedir` operates.
local AXIS_LOOKUP = { local AXIS_LOOKUP = {
vector.new( 0, 1, 0), -- +Y vector.new( 0, 1, 0), -- +Y
@ -50,14 +52,54 @@ for up_index, up in ipairs(AXIS_LOOKUP) do
end end
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. -- Returns whether the specified `node` is rotatable by this mod.
local function is_rotatable(node) local function is_rotatable(node)
local name = node and node.name local name = node and node.name or ""
local def = minetest.registered_nodes[name] return not not rotatable_registry[name]
return def and def.paramtype2 == "facedir"
end end
rotate.is_rotatable = is_rotatable 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) local function rotate_box_by_facedir(box, facedir)
if facedir == 0 then return end if facedir == 0 then return end
@ -83,24 +125,4 @@ local function rotate_box_by_facedir(box, facedir)
end end
rotate.rotate_box_by_facedir = rotate_box_by_facedir 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 return rotate

Loading…
Cancel
Save