Add screwdriver2 mod

This commit is contained in:
LoneWolfHT 2019-10-22 12:12:46 -07:00
parent 47f19ab7e4
commit 7c71f4dce6
9 changed files with 459 additions and 0 deletions

View File

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

View File

@ -0,0 +1,5 @@
Improved node rotation tool.
![bbcode>markdown](http://kland.smilebasicsource.com/i/kwdeu.png)
https://forum.minetest.net/viewtopic.php?f=9&t=20856

View File

@ -0,0 +1,3 @@
screwdriver?
worldedit?
default?

320
mods/screwdriver2/init.lua Normal file
View File

@ -0,0 +1,320 @@
if not minetest.raycast then
minetest.log("error", "screwdriver2 requires minetest version 5.0 or newer")
return
end
local function disp(...)
for _, x in ipairs({...}) do
minetest.chat_send_all(dump(x))
end
end
screwdriver2 = {}
local function rotate_simple(_, _, _, _, new_param2)
if new_param2 % 32 > 3 then return false end
end
-- If the screwdriver mod is not installed, create a fake screwdriver variable.
-- (This mod has an optional dependancy on screwdriver, so minetest loads screwdriver first if it exists.)
-- - Some mods will only set on_rotate when `screwdriver` exists.
-- - Mods may expect `screwdiver` to exist if `on_rotate` is called.
if not minetest.global_exists("screwdriver") then
screwdriver = {
ROTATE_FACE = 1,
ROTATE_AXIS = 2,
rotate_simple = rotate_simple,
disallow = false, -- I doubt anyone actually used screwdriver.disallow but whatever.
}
end
local get_pointed = dofile(minetest.get_modpath("screwdriver2").."/pointed.lua")
-- Functions to choose rotation based on pointed location
local insanity_2 = {xy = 1, yz = 1, zx = 1; zy = -1, yx = -1, xz = -1} -- Don't worry about this
local function push_edge(normal, point)
local biggest = 0
local biggest_axis
local normal_axis
-- Find the normal axis, and the axis of the with the
-- greatest magnitude (other than the normal axis)
for axis in pairs(point) do
if normal[axis] ~= 0 then
normal_axis = axis
elseif math.abs(point[axis])>biggest then
biggest = math.abs(point[axis])
biggest_axis = axis
end
end
-- Find the third axis, which is the one to rotate around
if normal_axis and biggest_axis then
for axis in pairs(point) do
if axis ~= normal_axis and axis ~= biggest_axis then
-- Decide which direction to rotate (+ or -)
return axis, insanity_2[normal_axis..biggest_axis] * math.sign(normal[normal_axis] * point[biggest_axis])
end
end
end
return "y", 0
end
local function rotate_face(normal, _)
-- Find the normal axis
for axis, value in pairs(normal) do
if value ~= 0 then
return axis, math.sign(value)
end
end
return "y", 0
end
-- Numbers taken from https://forum.minetest.net/viewtopic.php?p=73195&sid=1d2d2e4e76ce2ef9c84646481a4b84bc#p73195
-- "How to rotate (clockwise) by axis from any facedir:"
-- "(this will be made into a lua function)"
-- 5 years later...
local facedir_cycles = {
x = {{12,13,14,15},{16,19,18,17},{ 0, 4,22, 8},{ 1, 5,23, 9},{ 2, 6,20,10},{ 3, 7,21,11}},
y = {{ 0, 1, 2, 3},{20,23,22,21},{ 4,13,10,19},{ 8,17, 6,15},{12, 9,18, 7},{16, 5,14,11}},
z = {{ 4, 5, 6, 7},{ 8,11,10, 9},{ 0,16,20,12},{ 1,17,21,13},{ 2,18,22,14},{ 3,19,23,15}},
}
local wallmounted_cycles = {
x = {0, 4, 1, 5},
y = {4, 2, 5, 3},
z = {0, 3, 1, 2},
}
-- Functions to rotate a facedir/wallmounted value around an axis by a certain amount
local rotate = {
-- Facedir: lower 5 bits used for direction, 0 - 23
facedir = function(param2, axis, amount)
local facedir = param2 % 32
for _, cycle in ipairs(facedir_cycles[axis]) do
-- Find the current facedir
-- Minetest adds table.indexof, but I refuse to use it because it returns -1 rather than nil
for i, fd in ipairs(cycle) do
if fd == facedir then
return param2 - facedir + cycle[1+(i-1 + amount) % 4] -- If only Lua didn't use 1 indexing...
end
end
end
return param2
end,
-- Wallmounted: lower 3 bits used, 0 - 5
wallmounted = function(param2, axis, amount)
local wallmounted = param2 % 8
for i, wm in ipairs(wallmounted_cycles[axis]) do
if wm == wallmounted then
return param2 - wallmounted + wallmounted_cycles[axis][1+(i-1 + amount) % 4]
end
end
return param2
end
}
rotate.colorfacedir = rotate.facedir
rotate.colorwallmounted = rotate.wallmounted
--Todo: maybe support degrotate?
local function rect(angle, radius)
return math.cos(2*math.pi * angle) * radius, math.sin(2*math.pi * angle) * radius
end
-- Generate the screwdriver particle effects
local other_axes = {x = {"y","z"}, y = {"z","x"}, z = {"x","y"}}
local function particle_ring(pos, axis, direction)
local axis2, axis3 = unpack(other_axes[axis])
local particle_pos = vector.new()
local particle_vel = vector.new()
for i = 0, 0.999, 1/6 do
particle_pos[axis3], particle_pos[axis2] = rect(i, 0.5^0.5)
particle_vel[axis3], particle_vel[axis2] = rect(i - 1/4 * direction, 2)
minetest.add_particle({
pos = vector.add(pos, particle_pos),
velocity = particle_vel,
acceleration = vector.multiply(particle_pos, -7),
expirationtime = 0.25,
size = 2,
texture = "screwdriver2.png",
})
-- Smaller particles that last slightly longer, to give the illusion of
-- the particles disappearing smoothly
-- ?
-- minetest.add_particle({
-- pos = vector.add(pos, particle_pos),
-- velocity = particle_vel,
-- acceleration = vector.multiply(particle_pos, -7),
-- expirationtime = 0.3,
-- size = 1,
-- texture = "screwdriver2.png",
-- })
end
end
-- Decide what sound to make when rotating a node
local sound_groups = {"cracky", "crumbly", "dig_immediate", "metal", "choppy", "oddly_breakable_by_hand", "snappy"}
local function get_dig_sound(def)
if def.sounds and def.sounds.dig then
return def.sounds.dig
elseif not def.sound_dig or def.sound_dig == "__group" then
local groups = def.groups
for i, name in ipairs(sound_groups) do
if groups[name] and groups[name] > 0 then
return "default_dig_"..name
end
end
else
return def.sound_dig
end
end
-- Main
-- Idea: split this into 2 functions
-- 1: on_use parameters -> axis/amount/etc.
-- 2: param2/axis/amount/etc. -> new param2
function screwdriver.use(itemstack, player, pointed_thing, is_right_click)
if pointed_thing.type ~= "node" then return end
local pos = pointed_thing.under
-- Check protection
local player_name = player:get_player_name()
if minetest.is_protected(pos, player_name) then
minetest.record_protection_violation(pos, player_name)
return
end
-- Get node info
local node = minetest.get_node_or_nil(pos)
if not node then return end
local def = minetest.registered_nodes[node.name]
if not def then return end -- probably unnessesary
disp(def.sound_dig)
local on_rotate = def.on_rotate
if on_rotate == false then return end
--if on_rotate == nil and def.can_dig and not def.can_dig(vector.new(pos), player) then return end
-- Choose rotation function based on paramtype2 (facedir/wallmounted)
local rotate_function = rotate[def.paramtype2]
if not rotate_function then return end
-- Choose rotation axis/direction and param2 based on click type and pointed location
local axis, amount
local normal, point = get_pointed(player, pointed_thing)
if not normal or vector.length(normal) == 0 then return end -- Raycast failed or player is inside selection box
local control = player:get_player_control()
if is_right_click then
axis, amount = rotate_face(normal, point)
-- This line intentionally left blank.
else
axis, amount = push_edge(normal, point)
if control.sneak then amount = -amount end
end
local new_param2 = rotate_function(node.param2, axis, amount)
-- Calculate particle position
local particle_offset = vector.new()
particle_offset[axis] = point[axis]--math.sign(normal[axis]) * 0.5
-- Handle node's on_rotate function
local handled
if type(on_rotate) == "function" then
-- If a mod is loaded after screwdriver but before screwdriver2,
-- it will still end up using the old `rotate_simple` function.
-- So, we'll check that here, and override it in that case.
if on_rotate == screwdriver.rotate_simple then on_rotate = rotate_simple end
local result = on_rotate(
vector.new(pos),
table.copy(node),
player,
is_right_click and 2 or 1, -- Deprecated
new_param2,
-- New:
axis, -- "x", "y", or "z"
amount, -- 90 degrees = 1, etc.
rotate_function -- function(node.param2, axis, amount) -> new_param2
)
if result == false then
return
elseif result == true then
handled = true
end
end
-- Draw particles (Todo: check if rotation was actually done)
particle_ring(vector.add(pos, particle_offset), axis, amount)
-- Sound
local sound = get_dig_sound(def)
if sound then
minetest.sound_play(sound,{
pos = pos,
gain = 0.25,
max_hear_distance = 32,
})
end
-- Replace node
if not handled then
if new_param2 == node.param2 then return end -- no rotation was done
node.param2 = new_param2
minetest.swap_node(pos, node)
end
minetest.check_for_falling(pos)
if def._after_rotate then def._after_rotate(pos) end
-- Apply wear if not in creative mode
if not(creative and creative.is_enabled_for(player_name)) then
itemstack:add_wear(65535 / 200)
return itemstack
end
end
minetest.register_tool("screwdriver2:screwdriver",{
description = "Better Screwdriver\nleft click = push edge, right click = rotate face",
_doc_items_longdesc = "A tool for rotating nodes. Designed to be easier to use than the standard screwdriver.",
_doc_items_usagehelp = [[
Left clicking a node will "push" its nearest edge away from you. (Hold sneak to reverse the direction.)
Right click rotates the node clockwise around the face you are pointing at.]],
_doc_items_hidden = false,
inventory_image = "screwdriver2.png",
on_use = function(itemstack, player, pointed_thing)
return screwdriver.use(itemstack, player, pointed_thing, false)
end,
on_place = function(itemstack, player, pointed_thing)
return screwdriver.use(itemstack, player, pointed_thing, true)
end,
})
-- Just in case someone needs the old screwdriver, define a recipe to craft it.
if minetest.get_modpath("screwdriver") then
minetest.register_craft({
output = "screwdriver:screwdriver",
type = "shapeless",
recipe = {"screwdriver2:screwdriver"},
})
minetest.register_craft({
output = "screwdriver2:screwdriver",
type = "shapeless",
recipe = {"screwdriver:screwdriver"},
})
minetest.clear_craft({
recipe = {
{"default:steel_ingot"},
{"group:stick"},
},
})
end
-- Override screwdriver:screwdriver recipe:
minetest.register_craft({
output = "screwdriver2:screwdriver",
recipe = {
{"default:steel_ingot"},
{"group:stick"},
},
})
if minetest.get_modpath("worldedit") then
dofile(minetest.get_modpath("screwdriver2").."/worldedit.lua")
end

View File

@ -0,0 +1,3 @@
name = screwdriver2
description = A more intuitive node rotation tool.
optional_depends = screwdriver, worldedit_commands, default

View File

@ -0,0 +1,91 @@
-- returns function:
-- --------------------------------------------------------------------------
-- normal, point, box_id = function(player, pointed_thing)
-- ==========================================================================
-- normal - unit vector pointing out from the face which is pointed at
-- point - point (relative to the node position) which is being pointed at
-- box_id - index of the selection box which is being pointed at
-- ==========================================================================
-- Try to get the exact point the player is looking at.
-- There is some inaccuracy due to the client-side view bobbing animation.
-- To prevent the wrong node face from being found, it checks to make sure
-- the position returned by the raycaster matches pointed_thing.
-- If it doesn't match, the raycast is done again with slight offsets.
-- This will never return the WRONG node face, but may not be able to find the correct one in rare situations.
local function disp(...)
for _, x in ipairs({...}) do
minetest.chat_send_all(dump(x))
end
end
local bob_amount = (minetest.settings:get("view_bobbing_amount") or 1)
-- Calculate offsets for one cycle of the view bobbing animation
-- https://github.com/minetest/minetest/blob/b298b0339c79db7f5b3873e73ff9ea0130f05a8a/src/camera.cpp#L344
local check_points = {}
for i = 0, 0.99999, 1/20 do
local bobfrac = i * 2 % 1
local bobdir = i < 0.5 and 1 or -1
local bobtmp = math.sin(bobfrac ^ 1.2 * math.pi)
local x = (0.3 * bobdir * math.sin(bobfrac * math.pi)) * bob_amount/10 -- Why is this divided by 10?
local y = (-0.28 * bobtmp ^ 2) * bob_amount/10
-- I'm not exactly sure how the roll actually works, and it has a very small effect.
--local roll = -0.03 * bobdir * bobtmp * math.pi * bob_amount/10
table.insert(check_points, {
x = x,-- * math.cos(roll) - y * math.sin(roll),
y = y,-- * math.cos(roll) - x * math.sin(roll),
-- no Z offset
})
end
-- Get the start and end points for the raycaster
local function get_look_dir(player)
local placer_pos = player:get_pos()
placer_pos.y = placer_pos.y + player:get_properties().eye_height
return placer_pos, vector.multiply(player:get_look_dir(), 20)
end
local function try_raycast(pos, look_dir, pointed_thing, offset)
if offset then
--disp(offset.x .. " " .. offset.z)
pos = vector.add(pos, offset)
end
local raycast = minetest.raycast(pos, vector.add(pos, look_dir), false)
local pointed = raycast:next()
if pointed and pointed.type == "node" then
-- minetest.add_particle({
-- pos = pointed.intersection_point,
-- expirationtime = 5,
-- size = 0.1,
-- texture = "heart.png",
-- glow = 14,
-- })
if vector.equals(pointed.under, pointed_thing.under) and vector.equals(pointed.above, pointed_thing.above) then
return
pointed.intersection_normal,
vector.subtract(pointed.intersection_point, pointed.under),
pointed.box_id
end
end
end
-- Get the point the player is looking at
return function(player, pointed_thing)
local pos, look_dir = get_look_dir(player)
local pitch = player:get_look_vertical()
local yaw = player:get_look_horizontal()
--disp(angle)
for i, offset in ipairs(check_points) do
local a, b, c = try_raycast(pos, look_dir, pointed_thing, i > 1 and { -- (don't apply offset for the first check)
x = math.sin(-yaw) * math.sin(pitch) * offset.y + math.cos(yaw) * offset.x,
y = math.cos(pitch) * offset.y,
z = math.cos(-yaw) * math.sin(pitch) * offset.y + math.sin(yaw) * offset.x,
})
if a then return a, b, c end
end
minetest.log("warning", "Could not get pointed position")
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

View File

@ -0,0 +1,24 @@
local function disp(...)
for _, x in ipairs({...}) do
minetest.chat_send_all(dump(x))
end
end
local rotate_function = minetest.registered_chatcommands["/rotate"].func
minetest.register_node("screwdriver2:worldedit_screw",{
description = "WorldEdit Screw\nRotating this with the screwdriver will also rotate the worldedit region.",
tiles = {"default_stone.png^screwdriver2_screw.png"},
paramtype2 = "facedir",
groups = {cracky = 1, level = 2},
sounds = default and default.node_sound_metal_defaults(),
on_rotate = function(_, _, player, _, _, axis, amount)
local name = player:get_player_name()
if not minetest.check_player_privs(name, "worldedit") then
minetest.chat_send_player(name, "You don't have permission to use WorldEdit.")
return false
end
if axis ~= "y" then amount = -amount end
rotate_function(name, axis.." "..(amount * 90))
end,
})