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