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