- Use global to hold sub-modules - Move state calculation to state.lua - Do manual overriding of on_rightclick functions - Call default right-click impl when holding pin on doormain
parent
b0fc98b841
commit
04ca1950d7
6 changed files with 226 additions and 177 deletions
@ -1,104 +1,115 @@ |
|||||||
local minetest, vector, nodecore, include |
local ipairs, ItemStack, minetest, vector, include, nodecore |
||||||
= minetest, vector, nodecore, include |
= ipairs, ItemStack, minetest, vector, include, nodecore |
||||||
local math_round = math.round |
|
||||||
|
rawset(_G, "nc_extended_rotating", {}) |
||||||
local hud = include("hud") |
nc_extended_rotating.hud = include("hud") |
||||||
local rotate = include("rotate") |
nc_extended_rotating.registry = include("registry") |
||||||
local utility = include("utility") |
nc_extended_rotating.rotate = include("rotate") |
||||||
local registry = include("registry") |
nc_extended_rotating.utility = include("utility") -- Depends on `rotate`. |
||||||
|
nc_extended_rotating.state = include("state") -- Depends on `registry`, `rotate` and `utility`. |
||||||
|
|
||||||
|
local update_player_hud = nc_extended_rotating.hud.update_player_hud |
||||||
|
local is_rotatable = nc_extended_rotating.registry.is_rotatable |
||||||
|
local register_rotatable = nc_extended_rotating.registry.register_rotatable; |
||||||
|
local rotate_node = nc_extended_rotating.rotate.rotate_node |
||||||
|
local update_rotating_state = nc_extended_rotating.state.update_rotating_state |
||||||
|
local get_rotating_state = nc_extended_rotating.state.get_rotating_state |
||||||
|
|
||||||
|
-- TODO: Fix HUD showing rotation hint when we wouldn't / can't rotate. |
||||||
-- TODO: Add crosshair indicators for rotating around edges. |
-- TODO: Add crosshair indicators for rotating around edges. |
||||||
-- TODO: Add some more comments. |
-- TODO: Add some more comments. |
||||||
-- TODO: Add particles to preview rotation? |
-- TODO: Add particles to preview rotation? |
||||||
|
|
||||||
-- Distance at which we want to rotate by "pushing" on an edge. |
nodecore.register_playerstep({ |
||||||
local EDGE_DISTANCE = 2.5 / 16 -- texels (at 16² texture resolution) |
label = "nc_extended_rotating:update", |
||||||
|
action = function(player, data) |
||||||
-- Contains a per-player state that holds information, created by a raycast |
local state = update_rotating_state(player, data) |
||||||
-- done in `playerstep`, about the node this player might rotate. This is used |
update_player_hud(player, state) |
||||||
-- to update the their HUD and to decide what to do when a rightclick occurs. |
end, |
||||||
local rotating_state = {} |
}) |
||||||
|
|
||||||
|
-- Register rotatable nodes from NodeCore. |
||||||
|
do |
||||||
|
local function nodecore_filtered_lookup(eq_func) |
||||||
|
local lookup = {} |
||||||
|
for i = 0, 23 do |
||||||
|
local facedir = nodecore.facedirs[i] |
||||||
|
for j = 0, #lookup - 1 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 function handle_rotatable_rightclick(pos, node, clicker) |
local LENS_FILTERED_LOOKUP = nodecore_filtered_lookup( |
||||||
local state = rotating_state[clicker:get_player_name()] |
function(a, b) return vector.equals(a.f, b.f) end) |
||||||
if not state then return false end -- Not looking at anything rotatable. |
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) |
||||||
|
|
||||||
-- Make sure we're still the same node that we raycasted in `playerstep`. |
for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do |
||||||
if not vector.equals(pos, state.pos) then return true end |
register_rotatable("nc_optics:lens" .. lens_state, LENS_FILTERED_LOOKUP) end |
||||||
if node.name ~= state.node.name or node.param2 ~= state.node.param2 then return true end |
for _, prism_state in ipairs({ "", "_on", "_gated" }) do |
||||||
-- Only continue if the node can actually be sucessfully rotated. |
register_rotatable("nc_optics:prism" .. prism_state, PRISM_FILTERED_LOOKUP) end |
||||||
if not state.success then return true end |
|
||||||
|
|
||||||
rotate.rotate_node(pos, node, state.facedir) |
register_rotatable("nc_doors:panel_plank" , PANEL_FILTERED_LOOKUP) |
||||||
return true |
register_rotatable("nc_doors:panel_cobble", PANEL_FILTERED_LOOKUP) |
||||||
end |
end |
||||||
registry.custom_on_rightclick = handle_rotatable_rightclick |
|
||||||
|
|
||||||
-- Override `nc_scaling`'s default empty hand right-click behavior so we can use it |
local function handle_rightclick(pos, node, clicker) |
||||||
-- to rotate things when holding sneak, which doesn't trigger a node's `on_rightclick`. |
local state = get_rotating_state(clicker, pos, node) |
||||||
local default_on_place = minetest.registered_items[""].on_place |
if (not state) or (not state.success) then return end |
||||||
minetest.registered_items[""].on_place = function(item_stack, placer, pointed_thing, ...) |
rotate_node(pos, node, state.facedir) |
||||||
-- 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 |
end |
||||||
|
|
||||||
|
-- Replace `on_rightclick` of rotatable nodes from NodeCore. |
||||||
-- Calculates the state for the specified `player` and player `data`. |
do |
||||||
-- This state contains information about how the player would rotate a block. |
local function replace_optics_on_rightclick(name) |
||||||
local function calculate_rotating_state(player, data) |
local def = minetest.registered_nodes[name] |
||||||
local state = {} |
def.on_rightclick = handle_rightclick |
||||||
|
|
||||||
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 |
end |
||||||
|
|
||||||
-- Sneaking causes the direction of the rotation to be inverted. |
-- Panels can't have their `on_rightclick` overridden completely. |
||||||
if is_sneaking then degrees = -degrees end |
-- We need to call the default implementation when holding a "pin". |
||||||
state.invert = is_sneaking |
local function replace_panel_on_rightclick(name, pin) |
||||||
|
local def = minetest.registered_nodes[name] |
||||||
|
local default_rightclick = def.on_rightclick |
||||||
|
def.on_rightclick = function(pos, node, clicker, item_stack, ...) |
||||||
|
if nodecore.protection_test(pos, clicker) or ItemStack(item_stack):get_name() == pin |
||||||
|
then return default_rightclick(pos, node, clicker, item_stack, ...) |
||||||
|
else return handle_rightclick(pos, node, clicker) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
state.facedir = rotate.rotate_facedir(state.node.param2, state.axis, -degrees) |
for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do |
||||||
state.facedir = registry.fix_rotatable_facedir(state.node, state.facedir) |
replace_optics_on_rightclick("nc_optics:lens" .. lens_state) end |
||||||
state.success = state.facedir and state.facedir ~= state.node.param2 |
for _, lens_state in ipairs({ "", "_on", "_gated" }) do |
||||||
|
replace_optics_on_rightclick("nc_optics:prism" .. lens_state) end |
||||||
|
|
||||||
return state |
replace_panel_on_rightclick("nc_doors:panel_plank" , "nc_woodwork:staff") |
||||||
|
replace_panel_on_rightclick("nc_doors:panel_cobble", "nc_lode:rod_tempered") |
||||||
end |
end |
||||||
|
|
||||||
nodecore.register_playerstep({ |
-- Override `nc_scaling`'s default empty hand right-click behavior so we can use it |
||||||
label = "extended rotating update", |
-- to rotate things when holding sneak, which doesn't trigger a node's `on_rightclick`. |
||||||
action = function(player, data) |
local default_on_place = minetest.registered_items[""].on_place |
||||||
local name = player:get_player_name() |
minetest.registered_items[""].on_place = function(item_stack, placer, pointed_thing, ...) |
||||||
rotating_state[name] = calculate_rotating_state(player, data) |
if pointed_thing.type == "node" then |
||||||
hud.update_player_hud(player, rotating_state[name]) |
-- Player must sneak in order for this to run. |
||||||
end, |
-- Non-sneak is handled by node's `on_rightclick`. |
||||||
}) |
if minetest.is_player(placer) and placer:get_player_control().sneak then |
||||||
|
local pos = pointed_thing.under |
||||||
minetest.register_on_leaveplayer(function(player) |
local node = minetest.get_node(pos) |
||||||
local name = player:get_player_name() |
if is_rotatable(node) then |
||||||
rotating_state[name] = nil |
handle_rightclick(pos, node, placer) |
||||||
end) |
return -- Skip default behavior. |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
-- Call the default function, so we can still get the "scaling" functionality. |
||||||
|
default_on_place(item_stack, placer, pointed_thing, ...) |
||||||
|
end |
||||||
|
@ -1,73 +1,34 @@ |
|||||||
local minetest, nodecore |
local minetest |
||||||
= minetest, nodecore |
= minetest |
||||||
|
|
||||||
local registry = {} |
-- Table of nodes that can be rotated by the mod. |
||||||
local registered_rotatables = {} |
local registered_rotatables = {} |
||||||
|
|
||||||
-- Registered rotatable nodes will call this function when their `on_rightclick` |
|
||||||
-- is triggered instead of their default implementation. Can be overwritten. |
|
||||||
registry.custom_on_rightclick = function(pos, node, clicker, item_stack, pointed_thing) end |
|
||||||
|
|
||||||
local function register_rotatable(name, facedir_lookup) |
local registry = {} |
||||||
|
|
||||||
|
function registry.register_rotatable(name, facedir_lookup) |
||||||
local def = minetest.registered_nodes[name] |
local def = minetest.registered_nodes[name] |
||||||
if not def then error("Unknown node '" .. name .. "'") end |
if not def then error("Unknown node '" .. name .. "'") end |
||||||
if def.paramtype2 ~= "facedir" then error("Node '" .. name .. "' must be 'facedir'") end |
if def.paramtype2 ~= "facedir" then error("Node '" .. name .. "' must be 'facedir'") end |
||||||
def.on_rightclick = function(...) registry.custom_on_rightclick(...) end |
|
||||||
registered_rotatables[name] = { lookup = facedir_lookup } |
registered_rotatables[name] = { lookup = facedir_lookup } |
||||||
end |
end |
||||||
registry.register_rotatable = register_rotatable |
|
||||||
|
|
||||||
-- Returns whether the specified `node` is rotatable by this mod. |
-- Returns whether the specified `node` is rotatable by this mod. |
||||||
local function is_rotatable(node) |
function registry.is_rotatable(node) |
||||||
local name = node and node.name or "" |
return not not registered_rotatables[node.name] |
||||||
return not not registered_rotatables[name] |
|
||||||
end |
end |
||||||
registry.is_rotatable = is_rotatable |
|
||||||
|
|
||||||
-- Fixes facedir for the specified `node` when rotated to `facedir`. |
-- Fixes facedir for the specified `node` when rotated to `facedir`. |
||||||
-- * Returns `nil` if the node can't rotate this way. |
-- * Returns `nil` if the node can't rotate this way. |
||||||
-- * Returns `facedir` when the node can rotate this way. |
-- * Returns `facedir` when the node can rotate this way. |
||||||
-- * Returns another facedir when the node should rotate another equivalent way. |
-- * Returns another facedir when the node should rotate another equivalent way. |
||||||
local function fix_rotatable_facedir(node, facedir) |
function registry.fix_rotatable_facedir(node, facedir) |
||||||
local name = node and node.name or "" |
local name = node and node.name or "" |
||||||
local entry = registered_rotatables[name] |
local entry = registered_rotatables[name] |
||||||
if not entry then return nil end |
if not entry then return nil end |
||||||
if not entry.lookup then return facedir end |
if not entry.lookup then return facedir end |
||||||
return entry.lookup[facedir] |
return entry.lookup[facedir] |
||||||
end |
end |
||||||
registry.fix_rotatable_facedir = fix_rotatable_facedir |
|
||||||
|
|
||||||
|
|
||||||
-------------------------------------------- |
|
||||||
-- Register rotatable nodes from NodeCore -- |
|
||||||
-------------------------------------------- |
|
||||||
|
|
||||||
local function nodecore_filtered_lookup(eq_func) |
|
||||||
local lookup = {} |
|
||||||
for i = 0, 23 do |
|
||||||
local facedir = nodecore.facedirs[i] |
|
||||||
for j = 0, #lookup - 1 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 |
|
||||||
register_rotatable("nc_optics:lens" .. lens_state, LENS_FILTERED_LOOKUP) end |
|
||||||
for _, prism_state in ipairs({ "", "_on", "_gated" }) do |
|
||||||
register_rotatable("nc_optics:prism" .. prism_state, PRISM_FILTERED_LOOKUP) end |
|
||||||
|
|
||||||
register_rotatable("nc_doors:panel_plank" , PANEL_FILTERED_LOOKUP) |
|
||||||
register_rotatable("nc_doors:panel_cobble", PANEL_FILTERED_LOOKUP) |
|
||||||
|
|
||||||
return registry |
return registry |
||||||
|
@ -0,0 +1,83 @@ |
|||||||
|
local math_round, minetest, vector |
||||||
|
= math.round, minetest, vector |
||||||
|
|
||||||
|
local fix_rotatable_facedir = nc_extended_rotating.registry.fix_rotatable_facedir; |
||||||
|
local is_rotatable = nc_extended_rotating.registry.is_rotatable; |
||||||
|
local rotate_facedir = nc_extended_rotating.rotate.rotate_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 = {} |
||||||
|
|
||||||
|
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 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 = 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_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 |
||||||
|
|
||||||
|
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 |
Loading…
Reference in new issue