Rotate optics and doors from NodeCore with ease.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

115 lines
4.6 KiB

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: 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 = {}
-- 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
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,
})
minetest.register_on_leaveplayer(function(player)
local name = player:get_player_name()
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()
local state = rotating_state[name]
if not state then return end
-- Make sure we're still the same node that we raycasted in `playerstep`.
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
-- 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)