Alternative tool with swapped mouse buttons, custom recipes, readme updated with also a youtube video

master
entuland 2019-02-02 17:06:06 +01:00
parent 9fee3779d9
commit d777debbe9
10 changed files with 156 additions and 57 deletions

View File

@ -8,6 +8,8 @@ If you like my contributions you may consider reading http://entuland.com/en/sup
WIP MOD forum thread: https://forum.minetest.net/viewtopic.php?f=9&t=20321 WIP MOD forum thread: https://forum.minetest.net/viewtopic.php?f=9&t=20321
A silly, incomplete and unscripted video presentation of this mod: https://youtu.be/ESTJ9FYGHh4
# Dependencies # Dependencies
A thin wrapper around a very useful library to deal with Matrices: A thin wrapper around a very useful library to deal with Matrices:
@ -25,7 +27,7 @@ A thin wrapper around a very useful library to deal with Matrices:
# Why yet another screwdriver? # Why yet another screwdriver?
The default screwdriver included in minetest_game, as well as any other screwdriver mod I have found, operate differently depending on the node's direction and rotation. This means that any given click on a node may produce different results which you cannot predict at a glance. The default screwdriver included in minetest_game, as well as any other screwdriver mod I have found, operate differently depending on the node's direction and rotation. This means that any given click on a node may produce different results which you cannot predict at a glance, unless you're perfectly aware of where the node's main axis is pointing to.
The Rhotator Screwdriver uses a different approach: the direction and orientation of the node make absolutely no difference. The Rhotator Screwdriver uses a different approach: the direction and orientation of the node make absolutely no difference.
@ -38,11 +40,11 @@ These are the factors that affect the results of a click:
You will always be able to predict exactly the effect of the Rhotator Screwdriver. You will always be able to predict exactly the effect of the Rhotator Screwdriver.
Four consecutive clicks of the same button on the same position will always bring the node back to its original direction / orientation. Four consecutive clicks of the same button on the same position will always bring the node back to its original direction / orientation - or even less clicks, if you use the sneak key to invert the rotation direction.
### Why is it called "Rhotator" and not "Rotator"? ### Why is it called "Rhotator" and not "Rotator"?
In mathematics, the greek letter *Rho* is used to indicate some stuff associated to certain types of matrices. Since I'm using matrices to compute the various rotations in the game I thought about including it in the mod's name to reduce the chance of naming conflicts. In mathematics the greek letter *Rho* is used to indicate some stuff associated to certain types of matrices. Since I'm using matrices to compute the various rotations in the game I thought about including it in the mod's name to reduce the chance of naming conflicts.
# Appearance # Appearance
@ -58,7 +60,7 @@ The latter two types are handled exactly as the built-in screwdriver of `minetes
# Usage # Usage
Pretty simple: This is the behavior of the default `rhotator:screwdriver` tool:
- a right click will rotate the face you're pointing in clockwise direction - a right click will rotate the face you're pointing in clockwise direction
- the arrow in the Testing Cube shows how the face will rotate when right-clicked - the arrow in the Testing Cube shows how the face will rotate when right-clicked
@ -70,23 +72,31 @@ Pretty simple:
- `PS` in the tool stands for `push` - `PS` in the tool stands for `push`
- hold the sneak key down while clicking to "pull" instead of "pushing" - hold the sneak key down while clicking to "pull" instead of "pushing"
The left-click interaction area is not limited to the edges you can see in the Testing Cube. In reality you can click anywhere in a triangle like this (highlighted here just for convenience, you won't see anything like this in the game): (an alternative `rhotator:screwdriver_alt` tool is available with a sligthly different recipe, the buttons swapped and a corresponding texture with `RT` and `PS` swapped as well)
The `push` interaction area is not limited to the edges you can see in the Testing Cube. In reality you can click anywhere in a triangle like this (highlighted here just for convenience, you won't see anything like this in the game):
![Interaction triangle](/screenshots/interaction-triangle.png) ![Interaction triangle](/screenshots/interaction-triangle.png)
# Non-full nodes # Non-full nodes
Nodes that don't occupy a full cube (such as slabs and stairs) can still be rotated properly, it's enough that you pay attention to the direction of the part you're pointing at - the "stomp" parts of the stairs will behave as the "top" face, the "rise" parts will behave as the "front" face. With the Rhotator Screwdriver there never really is a "top" or a "front" or whatever: the only thing that matters is the face you're pointing at. Nodes that don't occupy a full cube (such as slabs and stairs) can still be rotated properly, it's enough that you pay attention to the direction of the part you're pointing at - the "stomp" parts of the stairs, for example, will behave as the "top" face, the "rise" parts will behave as the "front" face. With the Rhotator Screwdriver there never really is a "top" or a "front" or whatever: the only thing that matters is the face you're pointing at.
# Crafting # Crafting
Rhotator Screwdriver: a stick and a copper ingot; Rhotator Screwdriver: a stick and a copper ingot;
![Screwdriver crafting](/screenshots/screwdriver-crafting.png) ![Rhotator Screwdriver crafting](/screenshots/rhotator-recipe.png)
Rhotator Screwdriver Alt: two sticks and a copper ingot;
![Rhotator Screwdriver Alt crafting](/screenshots/rhotator-alt-recipe.png)
Rhotator Testing Cube: a Rhotator Screwdriver and any wool block Rhotator Testing Cube: a Rhotator Screwdriver and any wool block
![Testing cube crafting](/screenshots/testcube-crafting.png) ![Rhotator Testing Cube crafting](/screenshots/rhotator-cube-recipe.png)
Recipes can be customized by editing the `custom.recipes.lua` file that gets created in the mods' root folder upon first run.
# Chat commands # Chat commands
@ -95,16 +105,18 @@ Rhotator Testing Cube: a Rhotator Screwdriver and any wool block
- `rhotator memory on` enable placement memory - `rhotator memory on` enable placement memory
- `rhotator memory off` disable placement memory - `rhotator memory off` disable placement memory
Rotation memory starts off by default, it gets stored and recalled for each player between different sessions and between server restarts.
# Usage feedback # Usage feedback
An HUD message will show usage feedback, in particular it will inform you about nodes that aren't currently supported. An HUD message will show usage feedback, in particular it will inform about nodes that aren't currently supported.
Here are possible messages you can receive: Here are possible messages you can receive:
- Rotated pointed face clockwise (right click) - Rotated pointed face clockwise
- Rotated pointed face counter-clockwise (sneak + right click) - Rotated pointed face counter-clockwise
- Pushed closest edge (left click) - Pushed closest edge
- Pulled closest edge (sneak + left click) - Pulled closest edge
- Cannot rotate node with paramtype2 == glasslikeliquidlevel - Cannot rotate node with paramtype2 == glasslikeliquidlevel
- Unsupported node type: modname:nodename - Unsupported node type: modname:nodename
- Wallmounted node rotated with default screwdriver behavior - Wallmounted node rotated with default screwdriver behavior

2
default/README.txt Normal file
View File

@ -0,0 +1,2 @@
please do not edit any file in this folder,
corresponding custom.* files get created in the main mod's folder for you to customize

19
default/recipes.lua Normal file
View File

@ -0,0 +1,19 @@
-- only alter this file if it's named "custom.recipes.lua"
-- alter the recipes as you please and delete / comment out
-- the recipes you don't want to be available in the game
-- the original versions are in "default/recipes.lua"
return {
["rhotator:screwdriver"] = {
{"default:copper_ingot"},
{"group:stick"},
},
["rhotator:screwdriver_alt"] = {
{"default:copper_ingot", "group:stick"},
{"group:stick", ""},
},
["rhotator:cube"] = {
{"group:wool"},
{"rhotator:screwdriver"},
},
}

154
init.lua
View File

@ -185,41 +185,87 @@ local function vector_to_dir_index(vec)
return (vec.y > 0) and POS.Y or NEG.Y return (vec.y > 0) and POS.Y or NEG.Y
end end
-- ========================================================================
-- customization helpers
local function copy_file(source, dest)
local src_file = io.open(source, "rb")
if not src_file then
return false, "copy_file() unable to open source for reading"
end
local src_data = src_file:read("*all")
src_file:close()
local dest_file = io.open(dest, "wb")
if not dest_file then
return false, "copy_file() unable to open dest for writing"
end
dest_file:write(src_data)
dest_file:close()
return true, "files copied successfully"
end
local function custom_or_default(modname, path, filename)
local default_filename = "default/" .. filename
local full_filename = path .. "/custom." .. filename
local full_default_filename = path .. "/" .. default_filename
os.rename(path .. "/" .. filename, full_filename)
local file = io.open(full_filename, "rb")
if not file then
minetest.debug("[" .. modname .. "] Copying " .. default_filename .. " to " .. filename .. " (path: " .. path .. ")")
local success, err = copy_file(full_default_filename, full_filename)
if not success then
minetest.debug("[" .. modname .. "] " .. err)
return false
end
file = io.open(full_filename, "rb")
if not file then
minetest.debug("[" .. modname .. "] Unable to load " .. filename .. " file from path " .. path)
return false
end
end
file:close()
return full_filename
end
-- ============================================================ -- ============================================================
-- rhotator main -- rhotator main
local function rotate_main(param2_rotation, player, pointed_thing, click, rot_index) local function rotate_main(param2_rotation, player, pointed_thing, click, rot_index)
local unit = extract_unit_vectors(player, pointed_thing) local unit = extract_unit_vectors(player, pointed_thing)
local current_pos = pointed_thing.under local current_pos = pointed_thing.under
local message local message
local transform = false local transform = false
local rotation = rot_matrices[rot_index] local rotation = rot_matrices[rot_index]
local controls = player:get_player_control() local controls = player:get_player_control()
if click == PRIMARY_BTN then if click == PRIMARY_BTN then
transform = dir_matrices[vector_to_dir_index(unit.thumb)] transform = dir_matrices[vector_to_dir_index(unit.thumb)]
if controls.sneak then if controls.sneak then
rotation = rot_matrices[(rot_index + 2) % 4] rotation = rot_matrices[(rot_index + 2) % 4]
message = "Pulled closest edge (sneak + left click)" message = "Pulled closest edge"
else else
message = "Pushed closest edge (left click)" message = "Pushed closest edge"
end end
else else
transform = dir_matrices[vector_to_dir_index(unit.back)] transform = dir_matrices[vector_to_dir_index(unit.back)]
if controls.sneak then if controls.sneak then
rotation = rot_matrices[(rot_index + 2) % 4] rotation = rot_matrices[(rot_index + 2) % 4]
message = "Rotated pointed face counter-clockwise (sneak + right click)" message = "Rotated pointed face counter-clockwise"
else else
message = "Rotated pointed face clockwise (right click)" message = "Rotated pointed face clockwise"
end end
end end
local start = get_facedir_transform(param2_rotation) local start = get_facedir_transform(param2_rotation)
local stop = transform * rotation * transform:invert() * start local stop = transform * rotation * transform:invert() * start
return matrix_to_facedir(stop), message return matrix_to_facedir(stop), message
end end
-- ============================================================ -- ============================================================
@ -227,17 +273,17 @@ end
local handlers = {} local handlers = {}
function handlers.facedir(node, player, pointed_thing, click) function handlers.facedir(node, player, pointed_thing, click)
local rotation = node.param2 % 32 -- get first 5 bits local rotation = node.param2 % 32 -- get first 5 bits
local remaining = node.param2 - rotation local remaining = node.param2 - rotation
local rotate_90deg_clockwise = 1 local rotate_90deg_clockwise = 1
local rotation_result, message = rotate_main(rotation, player, pointed_thing, click, rotate_90deg_clockwise) local rotation_result, message = rotate_main(rotation, player, pointed_thing, click, rotate_90deg_clockwise)
local playername = player:get_player_name() local playername = player:get_player_name()
if storage:get_int("memory_" .. playername) == 1 then if storage:get_int("memory_" .. playername) == 1 then
facedir_memory[playername] = rotation_result facedir_memory[playername] = rotation_result
end end
return rotation_result + remaining, message return rotation_result + remaining, message
end end
@ -306,7 +352,7 @@ end
local function rhotator_on_placenode(pos, newnode, placer, oldnode, itemstack, pointed_thing) local function rhotator_on_placenode(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer or not placer.get_player_name then return end if not placer or not placer.get_player_name then return end
local playername = placer:get_player_name() local playername = placer:get_player_name()
local key = "memory_" .. playername local key = "memory_" .. playername
local memory = storage:get_int(key) == 1 local memory = storage:get_int(key) == 1
@ -314,21 +360,21 @@ local function rhotator_on_placenode(pos, newnode, placer, oldnode, itemstack, p
local new_rotation = facedir_memory[playername] local new_rotation = facedir_memory[playername]
if not new_rotation then return end if not new_rotation then return end
local nodedef = minetest.registered_nodes[newnode.name] local nodedef = minetest.registered_nodes[newnode.name]
if not nodedef then return end if not nodedef then return end
local paramtype2 = nodedef.paramtype2 local paramtype2 = nodedef.paramtype2
if paramtype2 ~= "facedir" and paramtype2 ~= "colorfacedir" then return end if paramtype2 ~= "facedir" and paramtype2 ~= "colorfacedir" then return end
local old_rotation = newnode.param2 % 32 -- get first 5 bits local old_rotation = newnode.param2 % 32 -- get first 5 bits
local remaining = newnode.param2 - old_rotation local remaining = newnode.param2 - old_rotation
newnode.param2 = new_rotation + remaining newnode.param2 = new_rotation + remaining
minetest.swap_node(pos, newnode) minetest.swap_node(pos, newnode)
minetest.check_for_falling(pos) minetest.check_for_falling(pos)
notify(placer, "Placed node according to previous rotation") notify(placer, "Placed node according to previous rotation")
end end
@ -344,7 +390,7 @@ function rhotator.command(playername, param)
local params = param:split(" ") local params = param:split(" ")
if params[1] == "memory" then if params[1] == "memory" then
command_memory(playername, params) command_memory(playername, params)
return return
end end
minetest.chat_send_player(playername, "[rhotator] unsupported param: " .. param) minetest.chat_send_player(playername, "[rhotator] unsupported param: " .. param)
@ -370,7 +416,7 @@ local function interact(player, pointed_thing, click)
end end
local handler = handlers[nodedef.paramtype2] local handler = handlers[nodedef.paramtype2]
-- Node provides a handler, so let the handler decide instead if the node can be rotated -- Node provides a handler, so let the handler decide instead if the node can be rotated
if nodedef.on_rotate then if nodedef.on_rotate then
-- Copy pos and node because callback can modify it -- Copy pos and node because callback can modify it
@ -394,7 +440,7 @@ local function interact(player, pointed_thing, click)
notify.warning(player, "Cannot rotate node with paramtype2 == " .. nodedef.paramtype2) notify.warning(player, "Cannot rotate node with paramtype2 == " .. nodedef.paramtype2)
return return
end end
local new_param2, handler_message = handler(node, player, pointed_thing, click) local new_param2, handler_message = handler(node, player, pointed_thing, click)
node.param2 = new_param2 node.param2 = new_param2
minetest.swap_node(pos, node) minetest.swap_node(pos, node)
@ -403,29 +449,32 @@ local function interact(player, pointed_thing, click)
if handler_message then if handler_message then
notify(player, handler_message) notify(player, handler_message)
end end
return return
end
local function primary_callback(itemstack, player, pointed_thing)
interact(player, pointed_thing, PRIMARY_BTN)
return itemstack
end
local function secondary_callback(itemstack, player, pointed_thing)
interact(player, pointed_thing, SECONDARY_BTN)
return itemstack
end end
minetest.register_tool("rhotator:screwdriver", { minetest.register_tool("rhotator:screwdriver", {
description = "Rhotator Screwdriver (left-click pushes edge, right-click rotates face)", description = "Rhotator Screwdriver\nLeft-click pushes edge\nRight-click rotates face\nHold sneak to invert direction",
inventory_image = "rhotator.png", inventory_image = "rhotator.png",
on_use = function(itemstack, player, pointed_thing) on_use = primary_callback,
interact(player, pointed_thing, PRIMARY_BTN) on_place = secondary_callback,
return itemstack
end,
on_place = function(itemstack, player, pointed_thing)
interact(player, pointed_thing, SECONDARY_BTN)
return itemstack
end,
}) })
minetest.register_craft({ minetest.register_tool("rhotator:screwdriver_alt", {
output = "rhotator:screwdriver", description = "Rhotator Screwdriver Alt\nLeft-click rotates face\nRight-click pushes edge\nHold sneak to invert direction",
recipe = { inventory_image = "rhotator-alt.png",
{"default:copper_ingot"}, on_use = secondary_callback,
{"group:stick"} on_place = primary_callback,
}
}) })
minetest.register_node("rhotator:cube", { minetest.register_node("rhotator:cube", {
@ -438,14 +487,31 @@ minetest.register_node("rhotator:cube", {
groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 }, groups = { snappy = 2, choppy = 2, oddly_breakable_by_hand = 3 },
}) })
minetest.register_craft({ local full_recipes_filename = custom_or_default("rhotator", mod_path, "recipes.lua")
output = "rhotator:cube", if not full_recipes_filename then
recipe = { error("[rhotator] unable to find " .. mod_path .. "/custom.recipes.lua")
{"group:wool"}, end
{"rhotator:screwdriver"},
} local recipes = dofile(full_recipes_filename);
})
if type(recipes) ~= "table" then
error("[rhotator] malformed file " .. mod_path .. "/custom.recipes.lua")
end
local expected_recipes = { "rhotator:screwdriver", "rhotator:screwdriver_alt", "rhotator:cube" }
for _, itemname in ipairs(expected_recipes) do
if type(recipes[itemname]) == "table" then
minetest.register_craft({
output = itemname,
recipe = recipes[itemname],
})
end
end
minetest.register_on_placenode(rhotator_on_placenode) minetest.register_on_placenode(rhotator_on_placenode)
minetest.register_chatcommand("rhotator", {func = rhotator.command}) minetest.register_chatcommand("rhotator", {
description = "memory [on|off]: displays or sets rotation memory for newly placed blocks",
func = rhotator.command
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

BIN
textures/rhotator-alt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B