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