Add screwdriver2 mod
This commit is contained in:
parent
47f19ab7e4
commit
7c71f4dce6
13
mods/screwdriver2/LICENSE.txt
Normal file
13
mods/screwdriver2/LICENSE.txt
Normal 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.
|
5
mods/screwdriver2/README.md
Normal file
5
mods/screwdriver2/README.md
Normal 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
|
3
mods/screwdriver2/depends.txt
Normal file
3
mods/screwdriver2/depends.txt
Normal file
@ -0,0 +1,3 @@
|
||||
screwdriver?
|
||||
worldedit?
|
||||
default?
|
320
mods/screwdriver2/init.lua
Normal file
320
mods/screwdriver2/init.lua
Normal 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
|
3
mods/screwdriver2/mod.conf
Normal file
3
mods/screwdriver2/mod.conf
Normal file
@ -0,0 +1,3 @@
|
||||
name = screwdriver2
|
||||
description = A more intuitive node rotation tool.
|
||||
optional_depends = screwdriver, worldedit_commands, default
|
91
mods/screwdriver2/pointed.lua
Normal file
91
mods/screwdriver2/pointed.lua
Normal 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
|
BIN
mods/screwdriver2/textures/screwdriver2.png
Normal file
BIN
mods/screwdriver2/textures/screwdriver2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 286 B |
BIN
mods/screwdriver2/textures/screwdriver2_screw.png
Normal file
BIN
mods/screwdriver2/textures/screwdriver2_screw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 273 B |
24
mods/screwdriver2/worldedit.lua
Normal file
24
mods/screwdriver2/worldedit.lua
Normal 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,
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user