local wea_c = worldeditadditions_core local Vector3 = wea_c.Vector3 -- ██████ ██████ ████████ █████ ████████ ███████ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██████ ██ ██ ██ ███████ ██ █████ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██ ██ ██████ ██ ██ ██ ██ ███████ worldeditadditions_core.register_command("rotate+", { params = " [ ...] [origin|o []]", description = "Rotates the defined region around the given axis by the given number of degrees. Angles are NOT limited to 90-degree increments. When multiple axes and angles are specified, these transformations are applied in order. If o [] is specified, then the specific position number (default: 3) is considered a custom rotation origin instead of the centre of the region. CAUTION: Rotating large areas of memory by 45° can be memory intensive!", privs = { worldedit = true }, require_pos = 2, parse = function (params_text) if not params_text then params_text = "" end local parts = wea_c.split_shell(params_text) print("DEBUG:rotate/parse parts", wea_c.inspect(parts)) local mode_store local mode = "AXIS" local success, axis_next, angle local origin = "__AUTO__" local rotlist = {} for i,part in ipairs(parts) do if part == "origin" or part == "o" then mode_store = mode -- If the NEXT item is a number, then parse it. if i < #parts and tonumber(parts[i+1]) ~= nil then mode = "ORIGIN" else -- If not, then default to pos3 and continue in the original mode origin = 3 end elseif mode == "ORIGIN" then origin = tonumber(part) if not origin or origin < 1 then return false, "Error: Expected positive integer as the WorldEditAdditions position that should be considered the origin point for rotation operation." end origin = math.floor(origin) mode = mode_store mode_store = nil elseif mode == "AXIS" then -- TODO: Somehow move this parsing ----> main func to get player reference to allow for relative stuff success, axis_next = wea_c.parse.axes_rotation.axis_name(part) if not success then return success, axis_next end mode = "ANGLE" elseif mode == "ANGLE" then angle = part -- 1: Determine if radians; strip suffix local pos_rad = part:find("ra?d?$") if pos_rad then angle = angle:sub(1, pos_rad-1) end -- 2: Parse as number angle = tonumber(angle) if not angle then return false, "Error: Expected numerical angle value, but found '"..tostring(part).."'." end -- 3: Convert degrees → radians if not pos_rad then -- We have degrees! Convert em to radians 'cauuse mathematics angle = math.rad(angle) end -- 4: Add to rotlist table.insert(rotlist, { axis = axis_next, rad = angle }) -- 5: Change mode and continue mode = "AXIS" else return false, "Error: Unknown parsing mode "..tostring(mode)..". This is a bug." end end print("DEBUG:rotate/parse ORIGIN", origin, "rotlist", wea_c.inspect(rotlist)) return true, origin, rotlist end, nodes_needed = function(name, origin, rotlist) -- BUG: .......this is a good question, actually. This naïve implementation is flawed, since if we rotate by e.g. 45° we could end up replacing more nodes than if we rotate by 90° increments. This is further complicated by the allowance of a custom point of origin. return Vector3.volume(wea_c.pos.get1(name), wea_c.pos.get2(name)) * 2 end, func = function(name, origin, rotlist) local start_time = wea_c.get_ms_time() ------------------------------------------------- local pos1, pos2 = wea_c.pos.get1(name), wea_c.pos.get2(name) local pos_origin = nil if type(origin) == "number" then pos_origin = wea_c.pos.get(name, origin) elseif type(origin) == "string" and origin == "__AUTO__" then pos_origin = Vector3.mean(pos1, pos2) end if pos_origin == nil then -- There's a very small chance that this could be a bug here if origin is (NOT type("number") and NOT type("string") and ~= "__AUTO__"), but this shouldn't happen because we constrain the output of the above parser. This means we can safely make this assumption here return false, "Error: Failed to get origin point from position "..tostring(origin).." because it doesn't exist." end local success, stats = worldeditadditions.rotate( pos1, pos2, pos_origin, rotlist ) if not success then return success, stats end wea_c.pos.set1(name, stats.pos1_dstvm) wea_c.pos.set2(name, stats.pos2_dstvm) -- TODO: Adjust the defined area to match the target here? Maybe make this optional somehow given the target may or may nor be axis-aligned ------------------------------------------------- local time_taken = wea_c.get_ms_time() - start_time minetest.log("action", name .. " used //rotate at "..pos1.." - "..pos2.." with origin "..pos_origin..", rotating "..stats.count_rotated.." nodes in "..time_taken.."s") return true, stats.count_rotated.." nodes rotated through "..tostring(#rotlist).." rotations in "..wea_c.format.human_time(time_taken) end })