diff --git a/init.lua b/init.lua index 66f477a..05cc293 100755 --- a/init.lua +++ b/init.lua @@ -1,25 +1,21 @@ -local ipairs, ItemStack, minetest, vector, include, nodecore - = ipairs, ItemStack, minetest, vector, include, nodecore +local ipairs, ItemStack, vector, include, nodecore + = ipairs, ItemStack, vector, include, nodecore rawset(_G, "nc_extended_rotating", {}) nc_extended_rotating.hud = include("hud") -nc_extended_rotating.registry = include("registry") nc_extended_rotating.rotate = include("rotate") -nc_extended_rotating.utility = include("utility") -- Depends on `rotate`. -nc_extended_rotating.entity = include("entity") -- Depends on `utility`. -nc_extended_rotating.state = include("state") -- Depends on `registry`, `rotate` and `utility`. +nc_extended_rotating.utility = include("utility") -- Depends on `rotate`. +nc_extended_rotating.entity = include("entity") -- Depends on `utility`. +nc_extended_rotating.registry = include("registry") -- Depends on `rotate` and `state` (late). +nc_extended_rotating.state = include("state") -- Depends on `registry`, `rotate` and `utility`. -local update_entity_hint = nc_extended_rotating.entity.update_entity_hint -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 +------------------------------------- +-- Register Player Update Function -- +------------------------------------- --- TODO: Fix HUD showing rotation hint when we wouldn't / can't rotate. --- TODO: Simplify registration and `on_rightclick` replacement into one call. --- TODO: Add some more comments. +local update_rotating_state = nc_extended_rotating.state.update_rotating_state +local update_player_hud = nc_extended_rotating.hud.update_player_hud +local update_entity_hint = nc_extended_rotating.entity.update_entity_hint nodecore.register_playerstep({ label = "nc_extended_rotating:update", @@ -30,106 +26,39 @@ nodecore.register_playerstep({ end, }) --- 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 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) +--------------------------------------- +-- Register Rotatable NodeCore Nodes -- +--------------------------------------- - for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do - register_rotatable("nc_optics:lens" .. lens_state, nil, LENS_FILTERED_LOOKUP) end - for _, prism_state in ipairs({ "", "_on", "_gated" }) do - register_rotatable("nc_optics:prism" .. prism_state, nil, PRISM_FILTERED_LOOKUP) end +local nodecore_filtered_lookup = nc_extended_rotating.utility.nodecore_filtered_lookup +local register_rotatable = nc_extended_rotating.registry.register_rotatable; - -- Register all existing door panels. - nodecore.register_on_register_item({ - retroactive = true, - func = function(name, def) - -- NOTE: This function is actually called BEFORE the item is registered with Minetest. - -- Because of this, looking up the node definition by name does not work. - name = name:gsub("^:", "") -- Fix for older versions of NodeCore. - if def.groups and def.groups.door_panel and (not def.groups.door) - then register_rotatable(name, def, PANEL_FILTERED_LOOKUP) end - end, - }) -end +-- Register lenses. +local LENS_FILTERED_LOOKUP = nodecore_filtered_lookup(function(a, b) + return vector.equals(a.f, b.f) end) +for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do + register_rotatable("nc_optics:lens" .. lens_state, nil, LENS_FILTERED_LOOKUP) end -local function handle_rightclick(pos, node, clicker) - local state = get_rotating_state(clicker, pos, node) - if (not state) or (not state.success) then return end - rotate_node(pos, node, state.facedir) -end +-- Register prisms. +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) +for _, prism_state in ipairs({ "", "_on", "_gated" }) do + register_rotatable("nc_optics:prism" .. prism_state, nil, PRISM_FILTERED_LOOKUP) end --- Replace `on_rightclick` of rotatable nodes from NodeCore. -do - local function replace_optics_on_rightclick(name) - local def = minetest.registered_nodes[name] - def.on_rightclick = handle_rightclick - end - - -- Panels can't have their `on_rightclick` overridden completely. - -- We need to call the default implementation when holding a "pin". - 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 +-- Register all existing and to-be-registered door panels. +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) +nodecore.register_on_register_item({ + retroactive = true, + func = function(name, def) + -- This function is actually called BEFORE the item is registered with Minetest. + -- Because of this, looking up the node definition by name does not work. + -- Thankfully, the panel is registered before the door, and as such it already exists. + if def.groups and def.groups.door then + local panel = def.drop_in_place.name -- The panel version of the door. + local pin = def.drop_non_silktouch or def.drop -- Dropped item is the door's pin. + register_rotatable(panel, nil, PANEL_FILTERED_LOOKUP, + function(_, _, _, item_stack) return ItemStack(item_stack):get_name() == pin end) end - end - - for _, lens_state in ipairs({ "", "_on", "_glow", "_glow_start" }) do - replace_optics_on_rightclick("nc_optics:lens" .. lens_state) end - for _, lens_state in ipairs({ "", "_on", "_gated" }) do - replace_optics_on_rightclick("nc_optics:prism" .. lens_state) end - - -- Replace `on_rightclick` for all existing doors' panels. - nodecore.register_on_register_item({ - retroactive = true, - func = function(name, def) - if def.groups and def.groups.door then - local panel = def.drop_in_place.name -- The panel version of the door. - local pin = def.drop_non_silktouch or def.drop -- Dropped item is the door's pin. - replace_panel_on_rightclick(panel, pin) - end - end, - }) -end - --- 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, ...) - if pointed_thing.type == "node" then - -- Player must sneak in order for this to run. - -- 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 - local node = minetest.get_node(pos) - if is_rotatable(node) then - handle_rightclick(pos, node, placer) - 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 + end, +}) diff --git a/registry.lua b/registry.lua index 927675b..f9958c0 100644 --- a/registry.lua +++ b/registry.lua @@ -1,17 +1,57 @@ -local minetest - = minetest +local minetest, nodecore + = minetest, nodecore -- Table of nodes that can be rotated by the mod. local registered_rotatables = {} - local registry = {} -function registry.register_rotatable(name, def, facedir_lookup) - def = def or minetest.registered_nodes[name] - if not def then error("Unknown node '" .. name .. "'") end +-- 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, ...) + if pointed_thing.type == "node" then + -- Player must sneak in order for this to run. + -- 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 + local node = minetest.get_node(pos) + if registry.is_rotatable(node) then + local def = minetest.registered_nodes[node.name] + def.on_rightclick(pos, node, placer, item_stack, pointed_thing) + 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 + +local rotate_node = nc_extended_rotating.rotate.rotate_node +-- local get_rotating_state = nc_extended_rotating.state.get_rotating_state +function registry.register_rotatable(name, def, facedir_lookup, rightclick_filter_func) + def = def or minetest.registered_nodes[name] or error("Unknown node '" .. name .. "'") if def.paramtype2 ~= "facedir" then error("Node '" .. name .. "' must be 'facedir'") end - registered_rotatables[name] = { lookup = facedir_lookup } + + -- Replace the default `on_rightclick` function so + -- we can call our custom rotation handling code. + local default_on_rightclick = def.on_rightclick + def.on_rightclick = function(pos, node, clicker, ...) + -- If `rightclick_filter_func` is set and returns true, call the default `on_rightclick`. + if registry.is_rightclick_filtered(pos, node, clicker, ...) then + default_on_rightclick(pos, node, clicker, ...) + else + if nodecore.protection_test(pos, clicker) then return end + local state = nc_extended_rotating.state.get_rotating_state(clicker, pos, node) + if (not state) or (not state.success) then return end + rotate_node(pos, node, state.facedir) + end + end + + registered_rotatables[name] = { + facedir_lookup = facedir_lookup, + rightclick_filter_func = rightclick_filter_func, + } end -- Returns whether the specified `node` is rotatable by this mod. @@ -19,6 +59,14 @@ function registry.is_rotatable(node) return registered_rotatables[node.name] ~= nil end +-- Returns whether the `on_rightclick` is filtered and the default +-- implementation should be called, instead of the custom rotation logic. +function registry.is_rightclick_filtered(pos, node, player, item_stack, pointed_thing) + local entry = registered_rotatables[node.name] + return entry and entry.rightclick_filter_func + and entry.rightclick_filter_func(pos, node, player, item_stack, pointed_thing) +end + -- Fixes facedir for the specified `node` when rotated to `facedir`. -- * Returns `nil` if the node can't rotate this way. -- * Returns `facedir` when the node can rotate this way. @@ -27,8 +75,8 @@ function registry.fix_rotatable_facedir(node, facedir) local name = node and node.name or "" local entry = registered_rotatables[name] if not entry then return nil end - if not entry.lookup then return facedir end - return entry.lookup[facedir] + if not entry.facedir_lookup then return facedir end + return entry.facedir_lookup[facedir] end return registry diff --git a/state.lua b/state.lua index 2792821..d4ebb02 100755 --- a/state.lua +++ b/state.lua @@ -2,6 +2,7 @@ local math_round, minetest, vector = math.round, minetest, vector local fix_rotatable_facedir = nc_extended_rotating.registry.fix_rotatable_facedir; +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 rotation_vector_from_lookat = nc_extended_rotating.utility.rotation_vector_from_lookat; @@ -32,17 +33,22 @@ local function calculate_rotating_state(player, data) 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 - if vector.equals(pointed_thing.above, pointed_thing.under) then return nil end - state.face = pointed_thing.above - pointed_thing.under + 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 player:get_wielded_item():is_empty()) then return nil end + 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) diff --git a/utility.lua b/utility.lua index 7ac5e13..1a41d1e 100644 --- a/utility.lua +++ b/utility.lua @@ -44,4 +44,17 @@ function utility.rotation_vector_from_lookat(node, pointed_thing, edge_distance) return b - math_abs(p) <= edge_distance and math_sign(p) or 0 end) end +function utility.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 + return utility