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)