From dacddb58a1f005cf0864d6c6a35ed55041909e61 Mon Sep 17 00:00:00 2001 From: Johannes Fritz Date: Fri, 8 Sep 2023 12:30:23 -0500 Subject: [PATCH] Add circle tool, mirror tool, screwdriver --- README.md | 40 ++++-- circle.lua | 143 ++++++++++++++++++++++ copy.lua | 7 +- fill.lua | 108 ++++++++-------- init.lua | 100 ++++++++++++++- mirror.lua | 210 ++++++++++++++++++++++++++++++++ open.lua | 8 ++ preview.lua | 18 ++- save.lua | 6 +- schematic.lua | 32 ++++- screwdriver.lua | 195 +++++++++++++++++++++++++++++ textures/edit_circle.png | Bin 0 -> 163 bytes textures/edit_mirror.png | Bin 0 -> 156 bytes textures/edit_mirror_border.png | Bin 0 -> 252 bytes textures/edit_screwdriver.png | Bin 0 -> 149 bytes 15 files changed, 782 insertions(+), 85 deletions(-) create mode 100644 circle.lua create mode 100644 mirror.lua create mode 100644 screwdriver.lua create mode 100644 textures/edit_circle.png create mode 100644 textures/edit_mirror.png create mode 100644 textures/edit_mirror_border.png create mode 100644 textures/edit_screwdriver.png diff --git a/README.md b/README.md index 7926a04..5006801 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,17 @@ 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) | +| 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. diff --git a/circle.lua b/circle.lua new file mode 100644 index 0000000..4f1d3bc --- /dev/null +++ b/circle.lua @@ -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, +}) diff --git a/copy.lua b/copy.lua index b6361e7..a142573 100644 --- a/copy.lua +++ b/copy.lua @@ -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 diff --git a/fill.lua b/fill.lua index dd2e6f1..81975b6 100644 --- a/fill.lua +++ b/fill.lua @@ -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 +}) + +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 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 + 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 + 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,28 +163,16 @@ 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 end return true -end) \ No newline at end of file +end) diff --git a/init.lua b/init.lua index 1f8ddab..4888bbd 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/mirror.lua b/mirror.lua new file mode 100644 index 0000000..1049c1b --- /dev/null +++ b/mirror.lua @@ -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 +}) diff --git a/open.lua b/open.lua index 0b4c451..0c470e9 100644 --- a/open.lua +++ b/open.lua @@ -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 diff --git a/preview.lua b/preview.lua index 8066dbf..e244fb9 100644 --- a/preview.lua +++ b/preview.lua @@ -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 diff --git a/save.lua b/save.lua index 273b7b4..1b6f38b 100644 --- a/save.lua +++ b/save.lua @@ -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 diff --git a/schematic.lua b/schematic.lua index a6f1199..540b55f 100644 --- a/schematic.lua +++ b/schematic.lua @@ -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) - meta:from_table(metadata) + 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 diff --git a/screwdriver.lua b/screwdriver.lua new file mode 100644 index 0000000..8d91e98 --- /dev/null +++ b/screwdriver.lua @@ -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, +}) diff --git a/textures/edit_circle.png b/textures/edit_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..4090f2a7347965222ea8c1826bb87a48190f10d0 GIT binary patch literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`S)MMAAr_~PPIlxv;K0G`d-DJO zIrF$2leoWbkXw9ionv6=Dp!Nfpfsi*Y#v>$!F+a2;$Px-yc78LZ289hPj)%aSi$Et z%Xvnc&t|`!tEUyp9Z0z{yYsH}vL`7?x2AtpS}1=xYwCX);cd@rm`&d%Yi_uBA{=Nd NgQu&X%Q~loCIHDqKxY5| literal 0 HcmV?d00001 diff --git a/textures/edit_mirror.png b/textures/edit_mirror.png new file mode 100644 index 0000000000000000000000000000000000000000..728966ca9fc9dfca9efefe298a1d029ffc4c5834 GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`$(}BbAr_~XPWEJCP~c!X`v3pU zb!n|3_i`BL@}!APQ%drh&S3C7QXzgFyMOh%9doX2V_IdrRw16nVW&m&(vFJfb!Qzk zqw^eRa=(|Er?^T<s*nhv0p)cE2x~t@0C(t?uPgg&ebxsLQ E0Iq5_B>(^b literal 0 HcmV?d00001 diff --git a/textures/edit_mirror_border.png b/textures/edit_mirror_border.png new file mode 100644 index 0000000000000000000000000000000000000000..837659ae1beb04a9b284b28229e78e002f38138d GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRRo-U3d6}R4ATPVn2z{70VKYwj- zT)-V~l~;^^PA;{c%KrO}Y=dphqVwzw^E22PT;|7qVO+rZW)=g({QU1f85%UUbUQKB z?7RD%86qlBTXvDXL2W}e6S62+Z3feBH3nQJ%#&p>X_RGf5b3mI_|REKq~;8!-|4>@ Y9a$dV-6F742Gn)MHA%&@a~QHczLnZ;X!*z;#+iMudRMY{P)o#& yCC6U8oWPsD|8