926 lines
32 KiB
Lua

local mod_name = "rotate"
local mod_name_upper=string.upper(mod_name)
if _G[mod_name] == nil then
error(string.format("[%s] global data not found - is the mod name correct ??", mod_name_upper))
end
local module = _G[mod_name]
module.api = {}
local PI = math.atan2(0,-1)
local opposite = {
north = "south",
south = "north",
east = "west",
west = "east",
up = "down",
down = "up",
front = "back",
back = "front",
top = "bottom",
bottom = "top",
left = "right",
right = "left",
}
local function dup_table(t)
local k,v
local dup = {}
for k,v in pairs(t) do
dup[k] = v
end
return t
end
local function mt_or11n_code(axis, rot)
return axis * 4 + rot % 4
end
local function mt_axis_code(or11n)
return math.floor(or11n / 4) % 8
end
local function mt_rot_code(or11n)
return or11n % 4
end
local or11n_directions = {"up", "north", "east"}
local or11n_code_prefix = "UNE="
local function or11n_code(node_or11n)
local or11n_prefix=""
local node_or11n_code=""
for i,dir in ipairs(or11n_directions) do
node_or11n_code = node_or11n_code .. "," .. node_or11n[dir]
end
node_or11n_code = or11n_code_prefix .. string.sub(node_or11n_code,2)
return node_or11n_code
end
local function rotate_node(node_or11n, rotation_spec)
local dir
local rotated_node_or11n = {}
for _,dir in ipairs(or11n_directions) do
if rotation_spec[dir] then
local new_dir = rotation_spec[dir]
if node_or11n[new_dir] then
rotated_node_or11n[new_dir] = node_or11n[dir]
else
rotated_node_or11n[opposite[new_dir]] = opposite[node_or11n[dir]]
end
else
rotated_node_or11n[dir] = node_or11n[dir]
end
end
return rotated_node_or11n
end
-- Define rotations when looking at a specific side of the object.
-- clockwise rotations are defined.
-- non-clockwise rotations are mapped to clockwise rotations
-- mappings have the following format:
-- - string: use the clockwise rotation for the specified side
-- - table: use the player's facing direction to find the side whose clockwise rotation to use
-- E.g.:
-- - Looking at the north side of a node, a clockwise rotation moves the
-- up side to the west, the west side down, the down side east and the east side up
-- - Looking at the west side of a node, a rotation upwards corresponds
-- to a clockwise rotation as seen from the south side of the node
-- (i.e.: up side goes east, east side goes down, etc.)
-- - Looking at the top side of the node, facing east, a rotation to the right
-- corresponds to a clockwise rotation as seen from the west side of the node
local rotation_specifications = {
north = {
cw = {up="west", west="down", down="east", east="up"},
ccw = "south",
up = "west",
down = "east",
left = "up",
right = "down",
},
south = {
cw = {up="east", east="down", down="west", west="up"},
ccw = "north",
up = "east",
down = "west",
left = "up",
right = "down",
},
east = {
cw = {up="north", north="down", down="south", south="up"},
ccw = "west",
up = "north",
down = "south",
left = "up",
right = "down",
},
west = {
cw = {up="south", south="down", down="north", north="up"},
ccw = "east",
up = "south",
down = "north",
left = "up",
right = "down",
},
up = {
cw = {north="east", east="south", south="west", west="north"},
ccw = "down",
up = {north="east", east="south", south="west", west="north"},
down = {north="west", west="south", south="east", east="north"},
left = {north="north", east="east", south="south", west="west"},
right = {north="south", east="west", south="north", west="east"},
},
down = {
cw = {north="west", west="south", south="east", east="north"},
ccw = "up",
up = {north="east", east="south", south="west", west="north"},
down = {north="west", west="south", south="east", east="north"},
left = {north="south", east="west", south="north", west="east"},
right = {north="north", east="east", south="south", west="west"},
},
}
-- Specification of minetest node orientation behavior
--
-- A node is defined as having 6 sides: left, right, front, back, top, bottom
-- Each of these could be oriented in one of 6 'compass' directions: north,
-- east, south, west, up or down.
-- The facing direction of 3 sides is sufficient to define the orientation of
-- a node.
--
-- Every orientation is specified by listing which sides of a node face in
-- each of 3 of the compass directions: up, north, east. E.g.:
-- {up="top", north="front", east="right"}
--
-- Every rotation is specfied by listing, for every compass direction, where
-- the side of the node that faced there previously will be facing next.
-- Directions which don't change don't need to be specified. E.g.:
-- {north="east", east="south", south="west", west="north"}
-- (i.e.: the rotation will move the side of the node that faced north, to
-- the west)
--
-- Minetest specifies the orientation of a node using two parameters:
-- - axis: specfiying the direction in which the originally vertical axis points
-- (6 values, as there are 6 possible directions)
-- - rotation: specifying the rotation around this axis, in 90-degrees steps
-- (0, 90, 180, 270 degrees - 4 possible rotations)
--
-- Table fields:
-- - axis_or11n: table specifying which side of an node faces which direction
-- with just the given axis orientation (i.e. rotation not changed)
-- - rot_cycle: table specifying how the sides change when rotation is applied
-- Default orientation of a node is defined as: {up="top", north="front", east="right"}
local mt_orientation = {
[0] = { axis_or11n={up="top", north="front", east="right"}, rot_cycle={north="east", east="south", south="west", west="north"} },
[1] = { axis_or11n={up="back", north="top", east="right"}, rot_cycle={up="west", west="down", down="east", east="up"} },
[2] = { axis_or11n={up="front", north="bottom", east="right"}, rot_cycle={down="west", west="up", up="east", east="down"} },
[3] = { axis_or11n={up="left", north="front", east="top"}, rot_cycle={north="down", down="south", south="up", up="north"} },
[4] = { axis_or11n={up="right", north="front", east="bottom"}, rot_cycle={north="up", up="south", south="down", down="north"} },
[5] = { axis_or11n={up="bottom", north="front", east="left"}, rot_cycle={north="west", west="south", south="east", east="north"} },
}
-- Table of mappings between minetest orientation code (0..23) and this mod's
-- own orientation code, and a corresponding node orientation
-- A node's orientation consists of a list of the sides facing up, north and east
-- shorthand code of the form 'UNE=<up-side>,<north-side>,<east-side>'
-- (contents are computed at startup, and only used at startup)
--
-- entries:
-- <int> = { code=<code>, node=<node orientation table> }
-- <code> = <int>
-- Example entries:
-- [0] = { code="UNE=top,front,right", node={up="top", north="front", east="right"},
-- ["UNE=top,front,right"] = 0
--
local mt_wrench_orientation_map = {}
-- Table of defined rotations - used at runtine to lookup the rotations
-- for every side of a node the user can be facing (punching) (north, east, ...), it contains a table
-- of minetest orientation code (0..23) to clockwise rotated minetest orientation code.
-- e.g.:
-- { north = { [0] = 1, [1] = 4, [...], [23] = 5 },
-- south = { [...] },
-- [...] }
-- (contents are computed at startup)
local mt_clockwise_rotation_map = {}
-- Mapping of minetest orientation to north-based orientation
-- (used for relative positioning mode)
-- (e.g.:
-- - If a block that is seen from the east must be looked
-- at from the north, it must be rotated right to have
-- exactly the same view.
-- - If a block that is seen from above by a player facing
-- east, must be looked from the north, it must be rotated
-- clockwise, then left to have exactly the same view).
local mt_orientation_to_facing = {
north = "none",
south = "reverse",
east = "right",
west = "left",
up = {
north = { "cw", "cw", "up" },
south = "down",
east = { "cw", "left" },
west = { "ccw", "right" },
},
down = {
north = { "cw", "cw", "down" },
south = "up",
east = { "ccw", "left" },
west = { "cw", "right" },
},
}
-- Reverse mapping of mt_orientation_to_facing
local facing_orientation_to_mt = {
north = "none",
south = "reverse",
east = "left",
west = "right",
up = {
north = { "down", "cw", "cw" },
south = "up",
east = { "right", "ccw" },
west = { "left", "cw" },
},
down = {
north = { "up", "cw", "cw" },
south = "down",
east = { "right", "cw" },
west = { "left", "ccw" },
},
}
-- Table of nodes previously rotated by users.
-- (used to avoid wearing the tool for multiple consecutive rotations of the same node)
local player_rotation_history = {}
-- fill mt_wrench_orientation_map
local function compute_wrench_orientation_codes()
local mt_axis
for mt_axis = 0,5 do
local mt_axis_spec = mt_orientation[mt_axis]
local mt_rot=0
local node_or11n = dup_table(mt_axis_spec.axis_or11n)
for mt_rot = 0, 3 do
local mt_orientation_code = mt_or11n_code(mt_axis, mt_rot)
local node_or11n_code=or11n_code(node_or11n)
mt_wrench_orientation_map[mt_orientation_code] = { code = node_or11n_code, node = node_or11n }
if not mt_wrench_orientation_map[node_or11n_code] then
--Different values of mt_orientation_code will map to the same code...
mt_wrench_orientation_map[node_or11n_code] = mt_orientation_code
end
node_or11n = rotate_node(node_or11n, mt_axis_spec.rot_cycle, true)
end
end
if module.debug >= 2 then
local k,v
for k,v in pairs(mt_wrench_orientation_map) do
if type(k) == "string" then
print(string.format("Wrench orientation[WR]: %s -> %d", k, v))
else
print(string.format("Wrench orientation[MT]: %d -> { %s %s %s (%s) }", k, v.node.up, v.node.north, v.node.east, v.code))
end
end
end
end
-- fill mt_clockwise_rotation_map
local function precompute_clockwise_rotations()
local mt_or11n
for mt_or11n = 0, 23 do
local facing
for facing, rotation_spec in pairs(rotation_specifications) do
local node_or11n = dup_table(mt_wrench_orientation_map[mt_or11n].node)
if not mt_clockwise_rotation_map[facing] then
mt_clockwise_rotation_map[facing] = {}
end
node_or11n = rotate_node(node_or11n, rotation_spec.cw, true)
local node_or11n_code = or11n_code(node_or11n)
mt_clockwise_rotation_map[facing][mt_or11n] = mt_wrench_orientation_map[node_or11n_code]
end
end
-- Just in case...:
-- for mt_or11n = 24, 31 do
-- for facing, rotation_spec in pairs(rotation_specifications) do
-- mt_clockwise_rotation_map[facing][mt_or11n] = 0
-- end
-- end
if module.debug >= 2 then
local k0, v0
for k0,v0 in pairs(mt_clockwise_rotation_map) do
io.write(string.format("%-10s:", k0))
local i
for i = 0, 23 do
if v0[i] then
io.write(string.format(" %2d",v0[i]))
else
io.write(string.format(" --"))
end
end
io.write("\n")
end
end
end
-- mapping of:
-- - <pitch quadrant> to facing direction
-- - <yaw quadrant,pitch quadrant> to faced side of the node
local quadrant_to_facing_map = {
["-2"] = "south",
["-1"] = "west",
["0"] = "north",
["1"] = "east",
["2"] = "south",
["-2,-1"] = "up",
["-1,-1"] = "up",
["0,-1"] = "up",
["1,-1"] = "up",
["2,-1"] = "up",
["-2,0"] = "north",
["-1,0"] = "east",
["0,0"] = "south",
["1,0"] = "west",
["2,0"] = "north",
["-2,1"] = "down",
["-1,1"] = "down",
["0,1"] = "down",
["1,1"] = "down",
["2,1"] = "down",
}
local dpos_to_pointing_map = {
["-1,0,0"] = "west",
["0,-1,0"] = "down",
["0,0,-1"] = "south",
["1,0,0"] = "east",
["0,1,0"] = "up",
["0,0,1"] = "north",
}
-- Given a pointed thing and a player, compute:
-- - the compass direction (NESW) the player is facing
-- - which side of the 'under' node the player is facing most
-- - which side of the 'under' node the player is pointing at
-- return:
-- { facing_direction = <direction (NESW)>, faced_side = <direction (NESWUD)>, pointed_side = <direction (NESWUD)> }
-- TODO: player provides methods for obtaining the pitch and yaw. Use those...
local function player_node_state(player, pointed_thing)
local c
local v
local player_pos = player:getpos()
local node_pos = pointed_thing.under
-- TODO: compute pitch based on actual eye position (is that possible at all ??)
player_pos.y = player_pos.y + module.api_config.eye_offset_hack
local node_dir = {}
for _,c in ipairs({"x", "y", "z"}) do
node_dir[c] = node_pos[c] - player_pos[c]
end
local result = {}
-- Compute facing direction
local yaw = math.atan2(node_dir.x, node_dir.z)
local hquadrant = math.floor((yaw + (PI/4)) / (PI/2))
result.facing_direction = quadrant_to_facing_map[string.format("%d",hquadrant)]
-- Compute faced side of the node
local pitch = math.atan2(node_dir.y, math.sqrt(node_dir.x*node_dir.x+node_dir.z*node_dir.z))
local vquadrant = math.floor((pitch + (PI/4)) / (PI/2))
result.faced_side = quadrant_to_facing_map[string.format("%d,%d",hquadrant,vquadrant)]
-- Compute pointed side of the node
local node_pos2 = pointed_thing.above
local dpos = {x = node_pos2.x-node_pos.x, y = node_pos2.y-node_pos.y, z = node_pos2.z-node_pos.z}
result.pointed_side = dpos_to_pointing_map[string.format("%d,%d,%d", dpos.x, dpos.y, dpos.z)]
if not result.faced_side or not result.facing_direction or not result.pointed_side then
error(string.format("[%s]: player_node_state: internal error: facing_direction = %s, faced_side=%s, pointed_side=%s",
mod_name_upper,result.facing_direction, result.faced_side, result.pointed_side))
end
return result
end
-- Given the player's state (see player_node_state()) and desired rotation
-- direction, lookup the equivalent clockwise rotation side of the node.
-- i.e. the side of the node, that will rotate clockwise.
local function clockwise_rotation_side(state, rotation)
local rot_side = state.pointed_side
if rotation ~= "cw" then
rot_side = rotation_specifications[rot_side][rotation]
if type(rot_side) == "table" then
rot_side = rot_side[state.facing_direction]
end
end
return rot_side
end
-- Convert a minetest absolute orientation to a north-based
-- orientation used for relative positioning mode
local function mt_to_relative_orientation(state, orientation)
local rotate = mt_orientation_to_facing[state.pointed_side]
if type(rotate) == "table" then
rotate = rotate[state.facing_direction]
end
if rotate == "none" then
rotate = {}
elseif rotate == "reverse" then
rotate = {"left", "left"}
elseif type(rotate) ~= "table" then
rotate = {rotate}
end
local step
for _,step in ipairs(rotate) do
local clockwise_side
clockwise_side = clockwise_rotation_side(state, step)
orientation = mt_clockwise_rotation_map[clockwise_side][orientation]
end
return orientation
end
-- Convert relative, north-based orientation to an absolute orientation
local function relative_to_mt_orientation(state, orientation)
local rotate = facing_orientation_to_mt[state.pointed_side]
if type(rotate) == "table" then
rotate = rotate[state.facing_direction]
end
if rotate == "none" then
rotate = {}
elseif rotate == "reverse" then
rotate = {"left", "left"}
elseif type(rotate) ~= "table" then
rotate = {rotate}
end
local step
for _,step in ipairs(rotate) do
local clockwise_side
clockwise_side = clockwise_rotation_side(state, step)
orientation = mt_clockwise_rotation_map[clockwise_side][orientation]
end
return orientation
end
-- Perform the actual rotation lookup (rotation mode). Pretty straightforward...
local function lookup_node_rotation(pointed_thing, old_orientation, player, rotation)
local state = player_node_state(player, pointed_thing)
local clockwise_side = clockwise_rotation_side(state, rotation)
return mt_clockwise_rotation_map[clockwise_side][old_orientation]
end
local function creative_mode(player)
-- support unified inventory's creative priv.
return minetest.setting_getbool("creative_mode") or
minetest.get_player_privs(player:get_player_name()).creative
end
local function register_rotation_privilege()
if module.api_config.privilege_name ~= nil then
minetest.register_privilege(module.api_config.privilege_name, "Can rotate nodes using the node rotation wrench")
end
end
local function has_rotation_privilege(player)
return module.api_config.privilege_name == nil
or minetest.get_player_privs(player:get_player_name())[module.api_config.privilege_name]
end
-- Check whether this is the same node as previously rotated
-- (if so, return true, else false)
-- and remember this node for the next time.
local function repeated_rotation(player, node, pos)
local player_name = player:get_player_name()
local old_history = player_rotation_history[player_name]
local new_history = {}
new_history.time = os.time()
new_history.node_name = node.name
new_history.node_pos = dup_table(pos)
player_rotation_history[player_name] = new_history
return old_history ~= nil
and new_history.time - old_history.time < 60
and new_history.node_name == old_history.node_name
and new_history.node_pos.x == old_history.node_pos.x
and new_history.node_pos.y == old_history.node_pos.y
and new_history.node_pos.z == old_history.node_pos.z
end
local function get_node_absolute_orientation_mode(pointed_thing)
if pointed_thing.type ~= "node" then
return
end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if not ndef or ndef.paramtype2 ~= "facedir" or
(ndef.drawtype == "nodebox" and
ndef.node_box.type ~= "fixed") or
node.param2 == nil then
return "a00"
else
local param2 = node.param2
local axis = mt_axis_code(param2)
local rot = mt_rot_code(param2)
return string.format("a%d%d",axis,rot)
end
end
local function get_node_relative_orientation_mode(player, pointed_thing)
if pointed_thing.type ~= "node" then
return
end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if not ndef or ndef.paramtype2 ~= "facedir" or
(ndef.drawtype == "nodebox" and
ndef.node_box.type ~= "fixed") or
node.param2 == nil then
return "r00"
else
local param2 = node.param2
local state = player_node_state(player, pointed_thing)
local relative = mt_to_relative_orientation(state, param2)
local axis = mt_axis_code(relative)
local rot = mt_rot_code(relative)
return string.format("r%d%d",axis,rot)
end
end
-- Main rotation function
local function wrench_handler(itemstack, player, pointed_thing, mode, material, max_uses)
if pointed_thing.type ~= "node" then
return
end
local pos = pointed_thing.under
if minetest.is_protected(pos, player:get_player_name()) then
minetest.record_protection_violation(pos, player:get_player_name())
return
end
if not has_rotation_privilege(player) then
minetest.chat_send_player(player:get_player_name(),"You are not allowed to rotate nodes")
return
end
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if not ndef or ndef.paramtype2 ~= "facedir" or
(ndef.drawtype == "nodebox" and
ndef.node_box.type ~= "fixed") or
node.param2 == nil then
return
end
-- Set param2
local old_param2 = node.param2
if string.match(mode, "[0-9]") then
local axis = tonumber(string.sub(mode, 2, 2))
local rot = tonumber(string.sub(mode, 3, 3))
local orientation = mt_or11n_code(axis, rot)
if string.sub(mode, 1, 1) == "a" then
node.param2 = orientation
elseif string.sub(mode, 1, 1) == "r" then
local state = player_node_state(player, pointed_thing)
node.param2 = relative_to_mt_orientation(state, orientation)
else
minetest.log("error", "Internal error: wrench has unrecognised mode ("..mode..")")
node.param2 = 0
end
else
node.param2 = lookup_node_rotation(pointed_thing, old_param2, player, mode)
end
if module.debug >= 1 then
minetest.chat_send_player(player:get_player_name(),
string.format("Node wrenched: axis %d, rot %d (%d) -> axis: %d, rot: %d (%d)",
mt_axis_code(old_param2),
mt_rot_code(old_param2),
old_param2,
mt_axis_code(node.param2),
mt_rot_code(node.param2),
node.param2))
end
minetest.swap_node(pos, node)
if not creative_mode(player) and not repeated_rotation(player, node, pos) then
-- 'ceil' ensures that the minimum wear is *always* 1
-- (and makes the tools wear a tiny bit faster)
itemstack:add_wear(math.ceil(65535 / max_uses))
end
return itemstack
end
-- Table of valid wrench modes, mapped to the next mode in the cycle
-- "" is the initial mode - which is non-operational (i.e. does nothing)
local wrench_modes = {
[""]="cw", cw="ccw", ccw="right", right="left", left="up", up="down", down="cw",
-- For the following, there exists no 'next mode'
["a00"]="a00", ["a01"]="a01", ["a02"]="a02", ["a03"]="a03",
["a10"]="a10", ["a11"]="a11", ["a12"]="a12", ["a13"]="a13",
["a20"]="a00", ["a21"]="a21", ["a22"]="a22", ["a23"]="a23",
["a30"]="a30", ["a31"]="a31", ["a32"]="a32", ["a33"]="a33",
["a40"]="a40", ["a41"]="a41", ["a42"]="a42", ["a43"]="a43",
["a50"]="a50", ["a51"]="a51", ["a52"]="a52", ["a53"]="a53",
["r00"]="r00", ["r01"]="r01", ["r02"]="r02", ["r03"]="r03",
["r10"]="r10", ["r11"]="r11", ["r12"]="r12", ["r13"]="r13",
["r20"]="r00", ["r21"]="r21", ["r22"]="r22", ["r23"]="r23",
["r30"]="r30", ["r31"]="r31", ["r32"]="r32", ["r33"]="r33",
["r40"]="r40", ["r41"]="r41", ["r42"]="r42", ["r43"]="r43",
["r50"]="r50", ["r51"]="r51", ["r52"]="r52", ["r53"]="r53",
}
local function compose_cube_image(mode)
local axis = string.sub(mode,2,2)
local rot = string.sub(mode,3,3)
local mt_mode = mt_or11n_code(axis, rot)
local composed_image = ""
local side, dir
for dir, side in pairs(mt_wrench_orientation_map[mt_mode].node) do
local image = "wrench_mode_"..side.."_"..dir..".png"
composed_image = composed_image.."^"..image
side = opposite[side]
dir = opposite[dir]
image = "wrench_mode_"..side.."_"..dir..".png"
composed_image = composed_image.."^"..image
end
return string.sub(composed_image,2)
end
local known_wrenches = {}
local function register_wrench_rotating(wrench_mod_name, material, material_descr, uses, mode, next_mode)
local sep = "_"
local notcrea = 1
local descr_extra = "; "
if mode == "" then
sep = ""
notcrea = 0
descr_extra = ""
end
known_wrenches[wrench_mod_name .. ":wrench_" .. material .. sep .. mode] = true
minetest.register_tool(wrench_mod_name .. ":wrench_" .. material .. sep .. mode, {
description = material_descr .. " wrench (" .. mode .. descr_extra .. "left-click rotates, right-click cycles mode)",
wield_image = "wrench_" .. material .. ".png",
inventory_image = "wrench_" .. material .. sep .. mode ..".png",
groups = { wrench = 1, ["wrench_"..material.."_rot"] = 1, not_in_creative_inventory = notcrea },
on_use = function(itemstack, player, pointed_thing)
if mode == "" then
minetest.chat_send_player(player:get_player_name(), "ALERT: Wrench is not configured yet. Right-click to set / cycle modes")
return
end
wrench_handler(itemstack, player, pointed_thing, mode, material, uses)
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
itemstack:set_name(wrench_mod_name .. ":wrench_" .. material .. "_" .. next_mode)
return itemstack
end,
})
end
local function register_wrench_positioning(wrench_mod_name, material, material_descr, uses, mode)
local notcrea = 1
if mode == "a00" or mode == "r00" then
notcrea = 0
end
local wrench_image = "wrench_" .. material ..".png"
local orientation_image
if module.api_config.wrench_orientation_indicator == "axis_rot" then
local axis_image = "wrench_axismode_" .. string.sub(mode, 2, 2) .. "_" .. string.sub(mode, 1, 1) .. "pos.png"
local rotation_image = "wrench_rotmode_" .. string.sub(mode, 3, 3) .. "_" .. string.sub(mode, 1, 1) .. "pos.png"
orientation_image = axis_image .. "^" .. rotation_image
elseif module.api_config.wrench_orientation_indicator == "linear" then
orientation_image = "wrench_mode_" .. mode.png
elseif module.api_config.wrench_orientation_indicator == "cube" then
local absrel_image = "wrench_mode_cube_"..string.sub(mode, 1, 1).."pos.png"
local sides_image = compose_cube_image(mode)
orientation_image = absrel_image .. "^" .. sides_image
else
error(string.format("[%s] unrecognised value for wrench_orientation_indicator: '%s'",mod_name_upper,module.api_config.wrench_orientation_indicator))
end
known_wrenches[wrench_mod_name .. ":wrench_" .. material .. "_" .. mode] = true
minetest.register_tool(wrench_mod_name .. ":wrench_" .. material .. "_" .. mode, {
description = material_descr .. " wrench (" .. mode .. "; left-click positions, right-click sets mode)",
wield_image = "wrench_" .. material .. ".png",
inventory_image = wrench_image .. "^" .. orientation_image,
groups = { wrench = 1, ["wrench_"..material.."_"..string.sub(mode,1,1).."pos"] = 1, not_in_creative_inventory = notcrea },
on_use = function(itemstack, player, pointed_thing)
wrench_handler(itemstack, player, pointed_thing, mode, material, uses)
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
local new_mode
if string.sub(mode,1,1) == "r" then
new_mode = get_node_relative_orientation_mode(player, pointed_thing)
else
new_mode = get_node_absolute_orientation_mode(pointed_thing)
end
itemstack:set_name(wrench_mod_name .. ":wrench_" .. material .. "_" .. new_mode)
return itemstack
end,
})
end
local function make_recipe(ingredient, dummy)
if module.api_config.craft_recipe == "beak_north" then
return {
{ingredient, dummy, ingredient},
{"", ingredient, "" },
{"", ingredient, "" },
}
elseif module.api_config.craft_recipe == "beak_northwest" then
return {
{dummy, ingredient, "" },
{ingredient, ingredient, "" },
{"", "", ingredient},
}
elseif module.api_config.craft_recipe == "beak_west" then
return {
{ingredient, "", "" },
{dummy, ingredient, ingredient},
{ingredient, "", "" },
}
elseif module.api_config.craft_recipe == "beak_southwest" then
return {
{"", "", ingredient},
{ingredient, ingredient, "" },
{dummy, ingredient, "" },
}
elseif module.api_config.craft_recipe == "beak_south" then
return {
{"", ingredient, "" },
{"", ingredient, "" },
{ingredient, dummy, ingredient},
}
else
error(string.format("[%s] unrecognised recipe selected: '%s'",mod_name_upper,module.api_config.craft_recipe))
end
end
local function register_new_wrench(wrench_spec)
local material = wrench_spec.material
local mode, next_mode
for mode,next_mode in pairs(wrench_modes) do
if string.match(mode, "[0-9]") then
register_wrench_positioning(wrench_spec.mod_name, material, wrench_spec.description, wrench_spec.uses, mode)
else
register_wrench_rotating(wrench_spec.mod_name, material, wrench_spec.description, wrench_spec.uses, mode, next_mode)
end
end
if wrench_spec.ingredient ~= nil then
minetest.register_craft({
output = wrench_spec.mod_name .. ":wrench_" .. material,
recipe = make_recipe(wrench_spec.ingredient, "")
})
if module.api_config.alt_recipe == true then
minetest.register_craft({
output = wrench_spec.mod_name .. ":wrench_" .. material,
recipe = make_recipe(wrench_spec.ingredient, "group:wood")
})
end
end
-- Convert rotating wrench to positioning wrench (relative mode)
minetest.register_craft({
output = wrench_spec.mod_name .. ":wrench_" .. material .. "_r00",
recipe = {{"group:wrench_" .. material .. "_rot"}},
})
-- Convert positioning wrench (relative mode) to positioning wrench (absolute mode)
minetest.register_craft({
output = wrench_spec.mod_name .. ":wrench_" .. material .. "_a00",
recipe = {{"group:wrench_" .. material .. "_rpos"}},
})
-- Convert positioning wrench (absolute mode) to rotating wrench
minetest.register_craft({
output = wrench_spec.mod_name .. ":wrench_" .. material,
recipe = {{"group:wrench_" .. material .. "_apos"}},
})
end
local function register_crafting_helper()
-- When crafting a wrench from a wrench, keep its wear...
minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
if known_wrenches[itemstack:get_name()] ~= nil then
local i, ingredient
for i = 1, 9 do
if old_craft_grid[i]:get_count() > 0 then
if ingredient == nil then
ingredient = old_craft_grid[i]
else
return
end
end
end
if known_wrenches[ingredient:get_name()] ~= nil then
itemstack:set_wear(ingredient:get_wear())
end
end
end)
end
local registered_wrench_materials = {}
----------------------------------
------- API functions ------------
----------------------------------
local function register_wrench_recipe(material, ingredient)
if registered_wrench_materials[material] == nil then
error(string.format("[%s] rotate.register_wrench_recipe can only be used to define recipies for predefined wrenches - use rotate.register_wrench()"
,mod_name_upper))
end
if minetest.registered_nodes[ingredient] == nil and minetest.registered_items[ingredient] == nil then
minetest.log("error",string.format("[%s] rotate.register_wrench_recipe: ingredient '%s' is not registered in minetest",mod_name_upper, tostring(ingredient)))
end
-- This allows adding a new recipe for an existing wrench, but in the table, the
-- previous ingredient will be overwritten. That's no problem, as it won't be used anyway
registered_wrench_materials[material].ingredient = ingredient
minetest.register_craft({
output = mod_name .. ":wrench_" .. material,
recipe = make_recipe(ingredient, "")
})
end
local function register_wrench_raw(spec, override)
if spec.mod_name == nil or minetest.get_modpath(spec.mod_name) == nil then
minetest.log("error",string.format("[%s] %s.register_wrench: provided mod name '%s' does not exist",mod_name_upper, spec.mod_name))
end
if registered_wrench_materials[spec.material] ~= nil and not override then
minetest.log("error",string.format("[%s] %s.register_wrench: a wrench of material '%s' already exists",mod_name_upper, mod_name, tostring(spec.material)))
return
end
if spec.ingredient ~= nil and minetest.registered_nodes[spec.ingredient] == nil and minetest.registered_items[spec.ingredient] == nil then
minetest.log("error",string.format("[%s] %s.register_wrench(%s): ingredient '%s' is not registered in minetest",
mod_name_upper, mod_name, spec.material, tostring(spec.ingredient)))
end
if math.floor(spec.use_parameter) ~= spec.use_parameter then
if spec.use_parameter <= 0 then
error(string.format("[%s] %s.add_wrench: use factor is zero or less (%s)",mod_name_upper, mod_name, tostring(spec.use_parameter)))
end
spec.uses = spec.use_parameter * module.api_config.wrench_uses_steel
else
if spec.use_parameter < 1 then
error(string.format("[%s] %s.add_wrench: number of uses is less than 1 (%s)",mod_name_upper, mod_name, tostring(spec.use_parameter)))
end
-- Use ceil, so that it has at least one use
spec.uses = spec.use_parameter
end
registered_wrench_materials[spec.material] = spec
register_new_wrench(spec)
end
local function register_wrench(mod_name, material, description, ingredient, use_parameter, override)
if type(mod_name) == "table" then
local param = mod_name
override = material
register_wrench_raw({
material = param.material.."", -- make sure it's a string
description = param.description.."", -- make sure it's a string
ingredient = param.ingredient, -- may be nil
use_parameter = param.use_parameter + 0, -- make sure it's a number
mod_name = param.mod_name.."", -- make sure it's a string
}, override)
else
register_wrench_raw({
material = material.."",
description = description.."",
ingredient = ingredient,
use_parameter = use_parameter + 0,
mod_name = mod_name.."",
}, override)
end
end
--
-- Setup / initialize api
--
-- Export api
module.api = {
register_wrench_recipe = register_wrench_recipe,
register_wrench = register_wrench,
-- FYI only. Not used by this module
wrench_uses_steel = module.wrenches_config.wrench_uses_steel,
}
compute_wrench_orientation_codes()
precompute_clockwise_rotations()
register_rotation_privilege()
register_crafting_helper()