Add circle tool, mirror tool, screwdriver

This commit is contained in:
Johannes Fritz 2023-09-08 12:30:23 -05:00
parent 9fa77d293f
commit dacddb58a1
15 changed files with 782 additions and 85 deletions

View File

@ -17,13 +17,16 @@ This mod was inspired by the Fill Start and Fill End blocks in Manic Digger.
## Items
| Name | Item ID | Image |
| ------ | ----------- | ----------------------------- |
| ----------- | ---------------- | ---------------------------------- |
| Copy | edit:copy | ![](textures/edit_copy.png) |
| Paste | edit:paste | ![](textures/edit_paste.png) |
| Fill | edit:fill | ![](textures/edit_fill.png) |
| Open | edit:open | ![](textures/edit_open.png) |
| Save | edit:save | ![](textures/edit_save.png) |
| Undo | edit:undo | ![](textures/edit_undo.png) |
| Circle | edit:circle | ![](textures/edit_circle.png) |
| Mirror | edit:mirror | ![](textures/edit_mirror.png) |
| Screwdriver | edit:screwdriver | ![](textures/edit_screwdriver.png) |
## Dependencies
@ -54,7 +57,7 @@ Once a second fill node is placed, a dialog appears listing all items in the pla
### Open Tool
Right click with this tool to load .we or .mts schematics from the the world subfolder `schems` for pasting.
Right click with this tool to load .we or .mts schematics from the world subfolder `schems` for pasting.
Large .we files may fail to load.
@ -71,7 +74,22 @@ Large .we files may fail to load.
Right click with this tool to undo a world modification like filling or pasting.
Use a second time to redo the undo.
Only the most resent world modification can be undone.
Only the most resent edit operation can be undone.
### Circle Tool
This tool is used to create round structures. Place the tool to activate circle mode. A center point marker is placed wherever the circle tool is placed. In circle mode, any node that is placed will be repeated in a circle around the center point. Node digging is also repeated in the same way. To place or dig a node without it repeating it in a circle, press the aux1 key (E) while placing or digging. To exit circle mode, punch the circle center marker.
### Mirror Tool
This tool is used to mirror the placement or digging of nodes. Place the tool to activate mirror mode. A center point marker is placed wherever the mirror tool is placed. In mirror mode all placed or dig nodes are mirrored. To place or dig a node without mirroring, press the aux1 key (E) while placing or digging. The mirror tool supports four modes, X, Z, X and Z, and eighths. To switch modes, right click the center marker. To exit mirror mode, punch the center marker.
### Screwdriver
This tool is used for rotating nodes that support rotation. Right clicking a node with the screwdriver rotates the node around the X or Z axis depending on the player's position. Left clicking a node with the screwdriver rotates the node clockwise around the Y axis. Param2 types `wallmounted`, `facedir`, and `degrotate` are supported. The node is rotated 90 degrees for all param2 types except `degrotate` where the node is rotated by either 1.5 or 15 degrees. If the aux1 key (E) is held while rotating a `degrotate` node, the rotation angle will be increased by 4x.
## Settings
@ -89,7 +107,7 @@ The maximum volume of any edit operation. Increase to allow larger operations.
### edit_fast_node_fill_threshold
When the fill operation has a larger volume then the specified number, fast node fill will be used.
To disable fast node placement, set the threshold to be equil to the max operation volume.
To disable fast node placement, set the threshold to be equal to the max operation volume.
To disable slow node placement, set the threshold to 0.
With fast node placement, callbacks are not called so some nodes might be broken.

143
circle.lua Normal file
View File

@ -0,0 +1,143 @@
local function place_circle(player, pos, node)
local player_data = edit.player_data[player]
if
not player or
player:get_player_control().aux1 or
not player_data or
not player_data.circle_luaentity
or player_data.ignore_node_placement
then return end
local center = player_data.circle_luaentity._pos
center.y = pos.y
local radius = vector.distance(center, pos)
if radius < 1 then
minetest.set_node(pos, node)
return
else
minetest.remove_node(pos)
end
local radius_rounded = math.ceil(radius) + 1
local size = vector.new(radius_rounded * 2, 1, radius_rounded * 2)
if size.x * size.y * size.z > edit.max_operation_volume then
edit.display_size_error(player)
return
end
player_data.undo_schematic = edit.schematic_from_map(
vector.subtract(vector.round(center), vector.new(radius_rounded, 0, radius_rounded)),
size
)
player_data.ignore_node_placement = true -- Stop infinite recursion
-- Midpoint circle algorithm
local x = radius
local z = 0
if center.z % 1 ~= 0 then -- Is the marker in the middle of a node?
z = z + 0.5
end
while x >= z do
for factor_x = -1, 1, 2 do
for factor_z = -1, 1, 2 do
local factor = vector.new(factor_x, 1, factor_z)
local offset1 = vector.new(x, 0, z)
offset1 = vector.new(
offset1.x * factor.x,
offset1.y * factor.y,
offset1.z * factor.z )
local pos1 = vector.add(center, offset1)
edit.place_node_like_player(player, node, pos1)
local offset2 = vector.new(
offset1.z,
offset1.y,
offset1.x
)
local pos2 = vector.add(center, offset2)
edit.place_node_like_player(player, node, pos2)
end
end
z = z + 1
while z * z + x * x > radius * radius do
x = x - 1
end
end
player_data.ignore_node_placement = false
end
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger or not digger:is_player() then return end
local player_data = edit.player_data[digger]
if player_data.ignore_node_placement then return end
return place_circle(digger, pos, {name = "air"})
end)
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer then return end
local player_data = edit.player_data[placer]
if player_data.ignore_node_placement then return end
return place_circle(placer, pos, newnode)
end)
local function circle_tool_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) then return end
local d = edit.player_data[player]
if d.circle_luaentity then
d.circle_luaentity.object:remove()
end
local pos = edit.get_half_node_pointed_pos(player)
d.circle_luaentity = edit.add_marker("edit:circle", pos, player)
d.circle_hud = player:hud_add({
hud_elem_type = "text",
text = "CIRCLE MODE\n\nPunch the circle center to exit.\nPress the aux1 key (E) while placing to bypass.",
position = {x = 0.5, y = 0.8},
z_index = 100,
number = 0xffffff
})
end
minetest.register_tool("edit:circle",{
description = "Edit Circle",
tiles = {"edit_circle.png"},
inventory_image = "edit_circle.png",
range = 10,
on_place = circle_tool_on_place,
on_secondary_use = circle_tool_on_place,
})
minetest.register_entity("edit:circle", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1},
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_circle.png",
"edit_circle.png",
"edit_circle.png",
"edit_circle.png",
"edit_circle.png",
"edit_circle.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
if player_data then
player_data.circle_luaentity = nil
self._placer:hud_remove(player_data.circle_hud)
player_data.circle_hud = nil
end
end,
})

View File

@ -37,12 +37,7 @@ local function copy_on_place(itemstack, player, pointed_thing)
player:get_player_name(),
vector_to_string(start) .. " to " .. vector_to_string(_end) .. " copied." )
else
local obj_ref = minetest.add_entity(pos, "edit:copy")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
d.copy_luaentity1 = luaentity
d.copy_luaentity1 = edit.add_marker("edit:copy", pos, player)
end
end

106
fill.lua
View File

@ -5,23 +5,21 @@ local function fill_on_place(itemstack, player, pointed_thing)
pointed_thing = edit.get_pointed_thing_node(player)
end
local itemstack, pos = minetest.item_place_node(itemstack, player, pointed_thing)
local pos = edit.pointed_thing_to_pos(pointed_thing)
local player_data = edit.player_data
if player_data[player].fill1_pos and pos then
local diff = vector.subtract(player_data[player].fill1_pos, pos)
local player_data = edit.player_data[player]
if player_data.fill1 and pos then
player_data.fill2 = edit.add_marker("edit:fill", pos, player)
if not player_data.fill2 then return end
local diff = vector.subtract(player_data.fill1._pos, pos)
local size = vector.add(vector.apply(diff, math.abs), 1)
if size.x * size.y * size.z > edit.max_operation_volume then
edit.display_size_error(player)
minetest.remove_node(player_data[player].fill1_pos)
player_data[player].fill1_pos = nil
minetest.remove_node(pos)
player_data.fill1.object:remove()
return
end
player_data[player].fill2_pos = pos
player_data[player].fill_pointed_thing = pointed_thing
local inv = minetest.get_inventory({type = "player", name = player:get_player_name()})
local formspec = "size[8,6]label[2,0.5;Select item for filling]button_exit[7,0;1,1;quit;X]"
for y = 1, 4 do
@ -38,38 +36,53 @@ local function fill_on_place(itemstack, player, pointed_thing)
end
minetest.show_formspec(player:get_player_name(), "edit:fill", formspec)
elseif pos then
player_data[player].fill1_pos = pos
player_data.fill1 = edit.add_marker("edit:fill", pos, player)
end
end
minetest.register_node("edit:fill",{
minetest.register_tool("edit:fill", {
description = "Edit Fill",
tiles = {"edit_fill.png"},
inventory_image = "edit_fill.png",
groups = {snappy = 2, oddly_breakable_by_hand = 3, dig_immediate = 3},
range = 10,
on_place = fill_on_place,
on_secondary_use = fill_on_place,
on_destruct = function(pos)
for player, data in pairs(edit.player_data) do
local p1 = data.fill1_pos
local p2 = data.fill2_pos
if p1 and vector.equals(p1, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p1)
return
end
if p2 and vector.equals(p2, pos) then
data.fill1_pos = nil
data.fill2_pos = nil
data.fill_pointed_thing = nil
minetest.remove_node(p2)
return
})
minetest.register_entity("edit:fill", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1 },
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
"edit_fill.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
self.remove_called = true
if player_data then
if player_data.fill1 and not player_data.fill1.remove_called then
player_data.fill1.object:remove()
end
if player_data.fill2 and not player_data.fill2.remove_called then
player_data.fill2.object:remove()
end
player_data.fill1 = nil
player_data.fill2 = nil
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
@ -78,21 +91,16 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
minetest.close_formspec(player:get_player_name(), "edit:fill")
local d = edit.player_data[player]
local p1 = d.fill1_pos
local p2 = d.fill2_pos
local pointed_thing = d.fill_pointed_thing
if
not p1 or not p2 or
not pointed_thing or
not d.fill1 or not d.fill2 or
not edit.has_privilege(player)
then return true end
d.fill1_pos = nil
d.fill2_pos = nil
d.fill_pointed_thing = nil
minetest.remove_node(p1)
minetest.remove_node(p2)
local p1 = d.fill1._pos
local p2 = d.fill2._pos
d.fill1.object:remove()
local name
local def
@ -117,7 +125,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
param2 = minetest.dir_to_wallmounted(player:get_look_dir(), true)
end
local on_place = def.on_place
local on_place = def.on_place or function() end
local start = vector.new(
math.min(p1.x, p2.x),
@ -155,25 +163,13 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
voxel_manip:write_to_map(true)
voxel_manip:update_liquids()
else
local node = {name = name, param2 = param2}
-- Work top to bottom so we can remove falling nodes
for x = _end.x, start.x, -1 do
for y = _end.y, start.y, -1 do
for z = _end.z, start.z, -1 do
local pos = vector.new(x, y, z)
if is_node then
minetest.set_node(pos, {name = name, param2 = param2})
else
minetest.remove_node(pos)
end
if on_place then
local itemstack = ItemStack(name)
pointed_thing.intersection_point = vector.new(x + 0.5, y, z + 0.5)
pointed_thing.above = pos
pointed_thing.under = pos
on_place(itemstack, player, pointed_thing)
end
edit.place_node_like_player(player, node, pos)
end
end
end

100
init.lua
View File

@ -44,7 +44,8 @@ end
minetest.register_on_joinplayer(function(player)
edit.player_data[player] = {
schematic_offset = vector.new(0, 0, 0)
schematic_offset = vector.new(0, 0, 0),
mirror_mode = "x",
}
end)
@ -60,6 +61,15 @@ minetest.register_on_leaveplayer(function(player)
if d.copy_luaentity1 then
d.copy_luaentity1.object:remove()
end
if d.circle_luaentity then
d.circle_luaentity.object:remove()
end
if d.fill1 then
d.fill1.object:remove()
end
if d.fill2 then
d.fill2.object:remove()
end
edit.player_data[player] = nil
end)
@ -76,7 +86,7 @@ function edit.get_pointed_thing_node(player)
end
end
local pos = vector.round(pos2)
return { type = "node", under = pos, above = pos }
return { type = "node", under = pos, above = pos, intersection_point = pos}
end
function edit.pointed_thing_to_pos(pointed_thing)
@ -95,6 +105,89 @@ function edit.pointed_thing_to_pos(pointed_thing)
end
end
function edit.get_half_node_pointed_pos(player)
local intersection_point = edit.get_pointed_thing_node(player).intersection_point
local pos = vector.round(intersection_point)
local pos_list = {
pos,
vector.add(pos, vector.new(0.5, 0, 0.5)),
vector.add(pos, vector.new(-0.5, 0, -0.5)),
vector.add(pos, vector.new(0.5, 0, -0.5)),
vector.add(pos, vector.new(-0.5, 0, 0.5))
}
local shortest_length = 1
local pos
for i, p in pairs(pos_list) do
local length = vector.length(vector.subtract(intersection_point, p))
if length < shortest_length then
shortest_length = length
pos = p
end
end
return pos
end
local old_register_on_dignode = minetest.register_on_dignode
local registered_on_dignode = {}
minetest.register_on_dignode = function(func)
table.insert(registered_on_dignode, func)
old_register_on_dignode(func)
end
local old_register_on_placenode = minetest.register_on_placenode
local registered_on_placenode = {}
minetest.register_on_placenode = function(func)
table.insert(registered_on_placenode, func)
old_register_on_placenode(func)
end
function edit.place_node_like_player(player, node, pos)
local def = minetest.registered_items[node.name]
local is_node = minetest.registered_nodes[node.name] ~= nil
local itemstack = ItemStack(node.name)
local pointed_thing = {
type = "node",
above = pos,
under = pos,
}
minetest.remove_node(pos)
def.on_place(itemstack, player, pointed_thing)
local new_node = minetest.get_node(pos)
if
is_node and new_node.name == "air"
and minetest.get_item_group(node.name, "falling_node") == 0
then
new_node.name = node.name
end
new_node.param2 = node.param2 or new_node.param2
minetest.swap_node(pos, new_node)
if node.name == "air" then
local oldnode = {name = "air"}
for i, func in pairs(registered_on_dignode) do
func(pos, oldnode, player)
end
elseif is_node then
local oldnode = {name = "air"}
for i, func in pairs(registered_on_placenode) do
func(pos, node, player, oldnode, itemstack, pointed_thing)
end
end
end
function edit.add_marker(id, pos, player)
local obj_ref = minetest.add_entity(pos, id)
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
return luaentity
end
edit.modpath = minetest.get_modpath("edit")
dofile(edit.modpath .. "/copy.lua")
dofile(edit.modpath .. "/fill.lua")
@ -104,3 +197,6 @@ dofile(edit.modpath .. "/preview.lua")
dofile(edit.modpath .. "/save.lua")
dofile(edit.modpath .. "/schematic.lua")
dofile(edit.modpath .. "/undo.lua")
dofile(edit.modpath .. "/circle.lua")
dofile(edit.modpath .. "/mirror.lua")
dofile(edit.modpath .. "/screwdriver.lua")

210
mirror.lua Normal file
View File

@ -0,0 +1,210 @@
local function do_mirror(player, pos, node)
local d = edit.player_data[player]
if
not player or
player:get_player_control().aux1 or
not d or
not d.mirror_luaentity
or d.ignore_node_placement
then return end
d.ignore_node_placement = true -- Stop infinite recursion
local center = d.mirror_luaentity._pos
local offset = vector.subtract(pos, center)
if d.mirror_mode == "x" then
offset.x = -offset.x
edit.place_node_like_player(player, node, vector.add(center, offset))
elseif d.mirror_mode == "z" then
offset.z = -offset.z
edit.place_node_like_player(player, node, vector.add(center, offset))
elseif d.mirror_mode == "xz" then
for i = 1, 4 do
local axis = "x"
if i % 2 == 0 then
axis = "z"
end
offset[axis] = -offset[axis]
edit.place_node_like_player(player, node, vector.add(center, offset))
end
elseif d.mirror_mode == "eighths" then
for i = 1, 8 do
local axis = "x"
if i % 2 == 0 then
axis = "z"
end
if i == 5 then
offset = vector.new(offset.z, offset.y, offset.x)
end
offset[axis] = -offset[axis]
edit.place_node_like_player(player, node, vector.add(center, offset))
end
end
d.ignore_node_placement = nil
end
minetest.register_on_dignode(function(pos, oldnode, digger)
if not digger or not digger:is_player() then return end
local player_data = edit.player_data[digger]
if player_data.ignore_node_placement then return end
return do_mirror(digger, pos, {name = "air"})
end)
minetest.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if not placer or not placer:is_player() then return end
local player_data = edit.player_data[placer]
if player_data.ignore_node_placement then return end
return do_mirror(placer, pos, newnode)
end)
local function mirror_tool_on_place(itemstack, player, pointed_thing)
if not edit.on_place_checks(player) or pointed_thing.type == "object" then return end
local d = edit.player_data[player]
if d.mirror_luaentity then
d.mirror_luaentity.object:remove()
end
local pos = edit.get_half_node_pointed_pos(player)
local obj_ref = minetest.add_entity(pos, "edit:mirror")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
luaentity._pos = pos
luaentity._placer = player
luaentity:_update_borders()
d.mirror_luaentity = luaentity
d.mirror_hud = player:hud_add({
hud_elem_type = "text",
text = "MIRROR MODE\n\nPunch center indicator to exit.\n" ..
"Right click the center indicator to switch modes.\n" ..
"Press the aux1 key (E) while placing to bypass.",
position = {x = 0.5, y = 0.8},
z_index = 100,
number = 0xffffff
})
end
minetest.register_tool("edit:mirror", {
description = "Edit Mirror",
tiles = {"edit_mirror.png"},
inventory_image = "edit_mirror.png",
range = 10,
on_place = mirror_tool_on_place,
on_secondary_use = mirror_tool_on_place,
})
minetest.register_entity("edit:mirror_border", {
initial_properties = {
visual = "cube",
visual_size = { x = 16, y = 16, z = 0},
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
pointable = false,
backface_culling = true,
textures = {
"edit_mirror_border.png",
"edit_mirror_border.png",
"edit_mirror_border.png",
"edit_mirror_border.png",
"edit_mirror_border.png",
"edit_mirror_border.png",
},
},
})
minetest.register_entity("edit:mirror", {
initial_properties = {
visual = "cube",
visual_size = { x = 1.1, y = 1.1},
physical = false,
collide_with_objects = false,
static_save = false,
use_texture_alpha = true,
glow = -1,
backface_culling = false,
hp_max = 1,
textures = {
"edit_mirror.png",
"edit_mirror.png",
"edit_mirror.png",
"edit_mirror.png",
"edit_mirror.png",
"edit_mirror.png",
},
},
on_deactivate = function(self)
local player_data = edit.player_data[self._placer]
if player_data then
player_data.mirror_luaentity = nil
self._placer:hud_remove(player_data.mirror_hud)
player_data.mirror_hud = nil
end
for i, luaentity in pairs(self._borders) do
luaentity.object:remove()
end
self._borders = {}
end,
on_rightclick = function(self, clicker)
local player_data = edit.player_data[self._placer]
if player_data.mirror_mode == "x" then
player_data.mirror_mode = "z"
elseif player_data.mirror_mode == "z" then
player_data.mirror_mode = "xz"
elseif player_data.mirror_mode == "xz" then
player_data.mirror_mode = "eighths"
elseif player_data.mirror_mode == "eighths" then
player_data.mirror_mode = "x"
end
self:_update_borders()
end,
_borders = {},
_update_borders = function(self)
local function invert_tex(luaentity)
local texs = luaentity.object:get_properties().textures
for i, tex in pairs(texs) do
texs[i] = tex .. "^[invert:rgb"
end
luaentity.object:set_properties({textures = texs})
end
local player_data = edit.player_data[self._placer]
for i, luaentity in pairs(self._borders) do
luaentity.object:remove()
end
self._borders = {}
if player_data.mirror_mode:find("x") then
local obj_ref = minetest.add_entity(self._pos, "edit:mirror_border")
if not obj_ref then return end
obj_ref:set_rotation(vector.new(0, math.pi / 2, 0))
local luaentity = obj_ref:get_luaentity()
table.insert(self._borders, luaentity)
end
if player_data.mirror_mode:find("z") then
local obj_ref = minetest.add_entity(self._pos, "edit:mirror_border")
if not obj_ref then return end
local luaentity = obj_ref:get_luaentity()
invert_tex(luaentity)
table.insert(self._borders, luaentity)
end
if player_data.mirror_mode == "eighths" then
for i = 0, 7 do
local obj_ref = minetest.add_entity(self._pos, "edit:mirror_border")
if not obj_ref then return end
obj_ref:set_rotation(vector.new(0, math.pi / 4 * i, 0))
local luaentity = obj_ref:get_luaentity()
if i % 2 == 1 then invert_tex(luaentity) end
table.insert(self._borders, luaentity)
end
end
end
})

View File

@ -37,6 +37,7 @@ local function read_minetest_schematic(file_path)
local schematic = minetest.read_schematic(file_path, {})
if schematic then
schematic._meta = {}
schematic._timers = {}
schematic._rotation = 0
end
return schematic
@ -64,6 +65,7 @@ local function read_world_edit_schematic(file_path)
local schem_data = {}
local meta = {}
local timers = {}
local size = vector.new(x_max + 1, y_max + 1, z_max + 1)
local start = vector.new(1, 1, 1)
@ -80,6 +82,11 @@ local function read_world_edit_schematic(file_path)
local key = minetest.hash_node_position(vector.new(x, y, z))
meta[key] = node.meta
end
if node.timer then
local key = minetest.hash_node_position(vector.new(x, y, z))
timers[key] = node.timer
end
end
-- Replace empty space with air nodes
@ -93,6 +100,7 @@ local function read_world_edit_schematic(file_path)
size = size,
data = schem_data,
_meta = meta,
_timers = timers,
_rotation = 0,
}
end

View File

@ -315,29 +315,35 @@ minetest.register_globalstep(function(dtime)
local node1_pos
local node2_pos
local pointed_pos
local use_under = false
local fill_selected = item == "edit:fill"
local copy_selected = item == "edit:copy"
local circle_selected = item == "edit:circle"
local mirror_selected = item == "edit:mirror"
if fill_selected then
node1_pos = d.fill1_pos
node2_pos = d.fill2_pos
if d.fill1 then
node1_pos = d.fill1._pos
end
if d.fill2 then
node2_pos = d.fill2._pos
end
elseif copy_selected then
if d.copy_luaentity1 then
node1_pos = d.copy_luaentity1._pos
end
use_under = true
end
if not node2_pos or not node1_pos then
local pointed_thing = edit.get_pointed_thing_node(player)
if use_under then
if circle_selected or mirror_selected then
pointed_pos = edit.get_half_node_pointed_pos(player)
elseif copy_selected then
pointed_pos = pointed_thing.under
else
pointed_pos = edit.pointed_thing_to_pos(pointed_thing)
end
end
if (fill_selected or copy_selected) and not node2_pos and pointed_pos then
if (fill_selected or copy_selected or circle_selected or mirror_selected) and not node2_pos and pointed_pos then
if not d.place_preview or not d.place_preview:get_pos() then
d.place_preview = minetest.add_entity(player:get_pos(), "edit:place_preview")
d.place_preview_shown = true

View File

@ -42,11 +42,12 @@ local function serialize_world_edit_schematic(schematic)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = schematic.size})
local data = schematic.data
local meta = schematic._meta
local timers = schematic._timers
for i in voxel_area:iterp(start, schematic.size) do
local pos = voxel_area:position(i)
local name = data[i].name
local meta_key = minetest.hash_node_position(pos)
local hash = minetest.hash_node_position(pos)
if name ~= "air" then
table.insert(we, {
x = pos.x - 1,
@ -54,7 +55,8 @@ local function serialize_world_edit_schematic(schematic)
z = pos.z - 1,
name = name,
param2 = data[i].param2,
meta = meta[meta_key]
meta = meta[hash],
timer = timers[hash]
})
end
end

View File

@ -3,6 +3,8 @@ function edit.schematic_from_map(pos, size)
schematic.size = size
schematic._pos = pos
schematic._meta = {}
schematic._timers = {}
schematic._rotation = 0
local start = vector.new(1, 1, 1)
local voxel_area = VoxelArea:new({MinEdge = start, MaxEdge = size})
@ -44,13 +46,20 @@ function edit.schematic_from_map(pos, size)
local key = minetest.hash_node_position(offset)
schematic._meta[key] = meta
end
local timer = minetest.get_node_timer(node_pos)
local timeout = timer:get_timeout()
if timeout ~= 0 then
local key = minetest.hash_node_position(offset)
local elapsed = timer:get_elapsed()
schematic._timers[key] = {timeout, elapsed}
end
end
return schematic
end
function edit.set_schematic_rotation(schematic, angle)
if not schematic._rotation then schematic._rotation = 0 end
schematic._rotation = schematic._rotation + angle
if schematic._rotation < 0 then
schematic._rotation = schematic._rotation + 360
@ -116,6 +125,25 @@ function edit.schematic_to_map(pos, schematic)
end
local node_pos = vector.add(pos, offset)
local meta = minetest.get_meta(node_pos)
if meta then
meta:from_table(metadata)
end
end
for hash, timer_data in pairs(schematic._timers) do
local offset = minetest.get_position_from_hash(hash)
offset = vector.subtract(offset, 1)
if schematic._rotation == 90 then
offset = vector.new(offset.z, offset.y, size.x - offset.x - 1)
elseif schematic._rotation == 180 then
offset = vector.new(size.x - offset.x - 1, offset.y, size.z - offset.z - 1)
elseif schematic._rotation == 270 then
offset = vector.new(size.z - offset.z - 1, offset.y, offset.x)
end
local node_pos = vector.add(pos, offset)
local timer = minetest.get_node_timer(node_pos)
if timer then
timer:set(timer_data[1], timer_data[2])
end
end
end

195
screwdriver.lua Normal file
View File

@ -0,0 +1,195 @@
-- param2 rotation table from:
-- https://forum.minetest.net/viewtopic.php?p=73195&sid=1d2d2e4e76ce2ef9c84646481a4b84bc#p73195
-- Axis * 4 + Rotation = Facedir
-- First in pair is axis
-- Second in pair is rotation
local raw_facedir = {
x = {
{{3, 0}, {3, 1}, {3, 2}, {3, 3}},
{{4, 0}, {4, 3}, {4, 2}, {4, 1}},
{{0, 0}, {1, 0}, {5, 2}, {2, 0}},
{{0, 1}, {1, 1}, {5, 3}, {2, 1}},
{{0, 2}, {1, 2}, {5, 0}, {2, 2}},
{{0, 3}, {1, 3}, {5, 1}, {2, 3}}
},
y = {
{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
{{5, 0}, {5, 3}, {5, 2}, {5, 1}},
{{1, 0}, {3, 1}, {2, 2}, {4, 3}},
{{2, 0}, {4, 1}, {1, 2}, {3, 3}},
{{3, 0}, {2, 1}, {4, 2}, {1, 3}},
{{4, 0}, {1, 1}, {3, 2}, {2, 3}}
},
z = {
{{1, 0}, {1, 1}, {1, 2}, {1, 3}},
{{2, 0}, {2, 3}, {2, 2}, {2, 1}},
{{0, 0}, {4, 0}, {5, 0}, {3, 0}},
{{0, 1}, {4, 1}, {5, 1}, {3, 1}},
{{0, 2}, {4, 2}, {5, 2}, {3, 2}},
{{0, 3}, {4, 3}, {5, 3}, {3, 3}}
}
}
local facedir_rot = {}
local function pair_to_param2(pair)
return pair[1] * 4 + pair[2]
end
for axis, raw_rot in pairs(raw_facedir) do
facedir_rot[axis] = {}
for j, pair_list in pairs(raw_rot) do
for i = 1, 4 do
local back_index = i - 1
if back_index == 0 then
back_index = 4
end
local next_index = i + 1
if next_index == 5 then
next_index = 1
end
local back = pair_to_param2(pair_list[back_index])
local next = pair_to_param2(pair_list[next_index])
local current = pair_to_param2(pair_list[i])
facedir_rot[axis][current] = {next, back}
end
end
end
local raw_wallmounted = {
x = {4, 0, 5, 1},
y = {2, 4, 3, 5},
z = {3, 0, 2, 1},
}
local wallmounted_rot = {}
for axis, rots in pairs(raw_wallmounted) do
wallmounted_rot[axis] = {}
local param2s_unused = {
[0] = true, [1] = true, [2] = true,
[3] = true, [4] = true, [5] = true}
for i = 1, 4 do
local back_index = i - 1
if back_index == 0 then
back_index = 4
end
local next_index = i + 1
if next_index == 5 then
next_index = 1
end
local current = rots[i]
local back = rots[back_index]
local next = rots[next_index]
wallmounted_rot[axis][current] = {back, next}
param2s_unused[current] = nil
end
for param2, bool in pairs(param2s_unused) do
wallmounted_rot[axis][param2] = {param2, param2}
end
end
function edit.rotate_param2(node, rot_vect)
local def = minetest.registered_items[node.name]
if not node.param2 or not def then return end
local paramtype2 = def.paramtype2
local is_wallmounted = paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted"
local is_facedir = paramtype2 == "facedir" or paramtype2 == "colorfacedir"
local rot_table = is_facedir and facedir_rot or wallmounted_rot
if is_facedir or is_wallmounted then
local param2_rot = node.param2 % 32 -- Get first 5 bits
if is_wallmounted then
param2_rot = node.param2 % 8 -- Get first 3 bits
end
local param2_other = node.param2 - param2_rot
for axis, target_rot in pairs(rot_vect) do
if target_rot ~= 0 then
local direction = math.sign(target_rot)
for rot = direction, target_rot / (math.pi / 2), direction do
if target_rot > 0 then
param2_rot = rot_table[axis][param2_rot][1]
else
param2_rot = rot_table[axis][param2_rot][2]
end
end
end
end
node.param2 = param2_other + param2_rot
elseif paramtype2 == "degrotate" or paramtype2 == "colordegrotate" then
local param2_rot
local deg_per_unit
if paramtype2 == "degrotate" then
param2_rot = node.param2
deg_per_unit = 1.5
else
param2_rot = node.param2 % 32 -- Get first 5 bits
deg_per_unit = 15
end
local param2_other = node.param2 - param2_rot
local rot = param2_rot * deg_per_unit / 180 * math.pi
rot = rot + rot_vect.y
rot = rot % (math.pi * 2)
if rot < 0 then
rot = rot + math.pi * 2
end
param2_rot = math.round(rot / math.pi * 180 / deg_per_unit)
node.param2 = param2_other + param2_rot
end
end
local function screwdriver_run(player, pointed_thing, rotate_y)
if not edit.on_place_checks(player) then return end
if pointed_thing.type ~= "node" then return end
local pos = pointed_thing.under
local node = minetest.get_node(pos)
local def = minetest.registered_items[node.name]
if not def then return end
local rot = vector.new(0, 0, 0)
local paramtype2 = def.paramtype2
if paramtype2 == "degrotate" or paramtype2 == "colordegrotate" then
if rotate_y then
local deg = paramtype2 == "degrotate" and 1.5 or 15
rot.y = deg / 180 * math.pi
if player:get_player_control().aux1 then
rot.y = rot.y * 4
end
end
else
if rotate_y then
rot = vector.new(0, math.pi / 2, 0)
else
local player_pos = player:get_pos()
local diff = vector.subtract(player_pos, pos)
local abs_diff = vector.apply(diff, math.abs)
if abs_diff.x > abs_diff.z then
local sign = (diff.x > 0) and 1 or -1
rot = vector.new(0, 0, math.pi / 2 * sign)
else
local sign = (diff.z < 0) and 1 or -1
rot = vector.new(math.pi / 2 * sign, 0, 0)
end
end
end
local old_node = table.copy(node)
edit.rotate_param2(node, rot)
if def.on_rotate then
def.on_rotate(pos, old_node, player, 1, node.param2)
end
minetest.swap_node(pos, node)
end
minetest.register_tool("edit:screwdriver", {
description = "Edit Screwdriver",
inventory_image = "edit_screwdriver.png",
on_use = function(itemstack, user, pointed_thing)
screwdriver_run(user, pointed_thing, true)
return itemstack
end,
on_place = function(itemstack, user, pointed_thing)
screwdriver_run(user, pointed_thing, false)
return itemstack
end,
})

BIN
textures/edit_circle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

BIN
textures/edit_mirror.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B