|
|
|
local minetest, vector, nodecore, include
|
|
|
|
= minetest, vector, nodecore, include
|
|
|
|
local math_round = math.round
|
|
|
|
|
|
|
|
local hud = include("hud")
|
|
|
|
local rotate = include("rotate")
|
|
|
|
local utility = include("utility")
|
|
|
|
local registry = include("registry")
|
|
|
|
|
|
|
|
-- TODO: Add crosshair indicators for rotating around edges.
|
|
|
|
-- TODO: Add some more comments.
|
|
|
|
-- TODO: Add particles to preview rotation?
|
|
|
|
|
|
|
|
-- Distance at which we want to rotate by "pushing" on an edge.
|
|
|
|
local EDGE_DISTANCE = 1.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 = {}
|
|
|
|
|
|
|
|
|
|
|
|
local function handle_rotatable_rightclick(pos, node, clicker)
|
|
|
|
local state = rotating_state[clicker:get_player_name()]
|
|
|
|
if not state then return false end -- Not looking at anything rotatable.
|
|
|
|
|
|
|
|
-- Make sure we're still the same node that we raycasted in `playerstep`.
|
|
|
|
if not vector.equals(pos, state.pos) then return true end
|
|
|
|
if node.name ~= state.node.name or node.param2 ~= state.node.param2 then return true end
|
|
|
|
-- Only continue if the node can actually be sucessfully rotated.
|
|
|
|
if not state.success then return true end
|
|
|
|
|
|
|
|
rotate.rotate_node(pos, node, state.facedir)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
registry.custom_on_rightclick = handle_rotatable_rightclick
|
|
|
|
|
|
|
|
-- Override `nc_scaling`'s default empty hand right-click behavior so we can use it
|
|
|
|
-- to rotate things when holding sneak, which doesn't trigger a node's `on_rightclick`.
|
|
|
|
local default_on_place = minetest.registered_items[""].on_place
|
|
|
|
minetest.registered_items[""].on_place = function(item_stack, placer, pointed_thing, ...)
|
|
|
|
-- Player must sneak in order for this to run. Non-sneak is handled by node's `on_rightclick`.
|
|
|
|
if pointed_thing.type == "node" and minetest.is_player(placer) and placer:get_player_control().sneak then
|
|
|
|
local pos = pointed_thing.under
|
|
|
|
local node = minetest.get_node(pos)
|
|
|
|
if handle_rotatable_rightclick(pos, node, placer) then return end
|
|
|
|
end
|
|
|
|
default_on_place(item_stack, placer, pointed_thing, ...)
|
|
|
|
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 nil end
|
|
|
|
|
|
|
|
state.pos = pointed_thing.under
|
|
|
|
state.node = minetest.get_node(state.pos)
|
|
|
|
if not registry.is_rotatable(state.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
|
|
|
|
|
|
|
|
-- 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 player:get_wielded_item():is_empty()) then return nil end
|
|
|
|
|
|
|
|
local degrees = 90
|
|
|
|
local r = utility.rotation_vector_from_lookat(state.node, pointed_thing, EDGE_DISTANCE)
|
|
|
|
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 = "mirror"; 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.rotate_facedir(state.node.param2, state.axis, -degrees)
|
|
|
|
state.facedir = registry.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)
|