local math_round, minetest, vector = math.round, minetest, vector local is_rightclick_filtered = nc_extended_rotating.registry.is_rightclick_filtered; local is_rotatable = nc_extended_rotating.registry.is_rotatable; local rotate_facedir = nc_extended_rotating.rotate.rotate_facedir; local fix_rotatable_facedir = nc_extended_rotating.utility.fix_rotatable_facedir; local rotation_vector_from_lookat = nc_extended_rotating.utility.rotation_vector_from_lookat; -- Distance at which we want to rotate by "pushing" on an edge. local EDGE_DISTANCE = 2.5 / 16 -- 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 update the their HUD and to decide what to do when a rightclick occurs. local rotating_state = {} -- 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 = {} -- { -- pos = position of the node to be rotated -- node = the node being rotated -- face = a vector pointing away from the face looked at -- edge = a vector pointing to the face, edge or corner looked at -- axis = calculated axis to rotate around -- invert = whether the rotation was inverted due to holding sneak -- facedir = the facedir value to set the node to on rotation -- success = whether rotating would be successful -- } local pointed_thing = data.raycast() if (not pointed_thing) or pointed_thing.type ~= "node" then return nil end if vector.equals(pointed_thing.above, pointed_thing.under) then return nil end state.face = pointed_thing.above - pointed_thing.under state.pos = pointed_thing.under state.node = minetest.get_node(state.pos) if not is_rotatable(state.node) then return nil end local wielded = player:get_wielded_item() -- When player is sneaking, must be empty-handed for rotating to work. local is_sneaking = player:get_player_control().sneak if is_sneaking and (not wielded:is_empty()) then return nil end -- Rightclick action is being filtered and the default `on_righclick` will be called instead. if is_rightclick_filtered(state.pos, state.node, player, wielded, pointed_thing) then return nil end local degrees = 90 local r = rotation_vector_from_lookat(state.node, pointed_thing, EDGE_DISTANCE) state.edge = r state.axis = r local num = math_round(r.x * r.x + r.y * r.y + r.z * r.z) -- Squared length. if num == 1 then state.mode = "face" elseif num == 2 then state.mode = "edge"; state.axis = state.face:cross(r) elseif num == 3 then state.mode = "corner"; degrees = 120 end -- Sneaking causes the direction of the rotation to be inverted. if is_sneaking then degrees = -degrees end state.invert = is_sneaking state.facedir = rotate_facedir(state.node.param2, state.axis, -degrees) state.facedir = fix_rotatable_facedir(state.node, state.facedir) state.success = state.facedir and state.facedir ~= state.node.param2 -- TODO: Until we have a way to display "rotation non-success", don't show rotation hint at all. if not state.success then return nil end return state end minetest.register_on_leaveplayer(function(player) local name = player:get_player_name() rotating_state[name] = nil end) local state = {} function state.update_rotating_state(player, data) local rot_state = calculate_rotating_state(player, data) rotating_state[player:get_player_name()] = rot_state return rot_state end function state.get_rotating_state(player, pos, node) if not minetest.is_player(player) then return nil end local rot_state = rotating_state[player:get_player_name()] if not rot_state then return nil end if not vector.equals(pos, rot_state.pos) then return nil end if node.name ~= rot_state.node.name or node.param2 ~= rot_state.node.param2 then return nil end return rot_state end return state