local player = require("player") local world = require("world") local creator = require("creator") local keys = require("keys") local rotations = require("rotations_v3") rotations.setRotationSpeed(14) rotations.setModifier(3) local enabled = false -- Настраиваемый шаг в градусах для поиска блоков local step = 7 -- Диапазон случайной задержки в тиках перед сменой следующей точки (1 секунда = 20 тиков) local minShiftDelay = 10 -- 0.5 секунды local maxShiftDelay = 15 -- 0.75 секунды -- Список идентификаторов блоков для поиска local targetIdentifiers = { ["minecraft:coal_block"] = true, ["minecraft:iron_block"] = true, ["minecraft:redstone_block"] = true, ["minecraft:gold_block"] = true, ["minecraft:diamond_block"] = true, ["minecraft:lapis_block"] = true, ["minecraft:emerald_block"] = true, -- Mithril and Titanium ["minecraft:cyan_terracotta"] = true, ["minecraft:light_blue_wool"] = true, ["minecraft:polished_diorite"] = true, ["minecraft:dark_prismarine"] = true, ["minecraft:gray_wool"] = true, ["minecraft:prismarine"] = true, ["minecraft:prismarine_bricks"] = true, -- Сюда можно добавить любые другие ID } -- Переменные состояния local currentTarget = nil -- Текущая цель {pos, identifier, angles, targetRot, hasArrived, nextAimShiftTick, nextYaw, nextPitch, history} local currentTargetTicks = 0 -- Время удержания блока в тиках local lastActiveRot = nil -- Последняя отправленная ротация для непрерывного удержания взгляда local ignoredBlocks = {} -- Черный список застрявших блоков local globalTicks = 0 -- Счетчик тиков local nextTargetAllowedTick = 0 -- Тик, начиная с которого разрешено искать новую цель local scannedBlocks = {} -- Список всех найденных блоков для подсветки local nextTargetVisual = nil -- Предсказанная следующая точка для желтой подсветки registerServerSideRotationEvent(function(yaw, pitch) if yaw < -180 or yaw > 180 then return end if pitch < -90 or pitch > 90 then return end local rot = player.getRotation() if rot.yaw == yaw and rot.pitch == pitch then return end enabled = false player.addMessage("You got rotated") local pos = player.getPos() world.playSound(pos.x, pos.y, pos.z, "minecraft:block.anvil.place", 100, 1) end) -- Функция поиска блоков с группировкой по координатам и сохранением безопасных углов local function getBlocks(currentYaw, currentPitch) local headPos = player.getEyePosition() local blocksMap = {} local blocksList = {} for yaw = -180, 180, step do for pitch = 90, -90, -step do local result = world.raycastFromRotation({ startX = headPos.x, startY = headPos.y, startX = headPos.x, startY = headPos.y, startZ = headPos.z, yaw = yaw, pitch = pitch, range = 4.5, include_fluid = false, include_entity = true, }) if result and result.blockPos and result.location then local blockPos = result.blockPos local block = world.getBlock(blockPos.x, blockPos.y, blockPos.z) if block and block.identifier then local bx = math.floor(blockPos.x) local by = math.floor(blockPos.y) local bz = math.floor(blockPos.z) local key = bx .. "," .. by .. "," .. bz -- Проверяем черный список if not ignoredBlocks[key] or globalTicks >= ignoredBlocks[key] then if targetIdentifiers[block.identifier] then -- Считаем угловую разницу с текущим взглядом игрока local dyaw = yaw - currentYaw if dyaw > 180 then dyaw = dyaw - 360 end if dyaw < -180 then dyaw = dyaw + 360 end local dpitch = pitch - currentPitch local angleDiff = dyaw * dyaw + dpitch * dpitch -- Расчет безопасного отступа от краев хитбокса local loc = result.location local fx = loc.x - bx local fy = loc.y - by local fz = loc.z - bz -- Расстояния до ближайших границ блока local dist_x = math.min(fx, 1 - fx) local dist_y = math.min(fy, 1 - fy) local dist_z = math.min(fz, 1 - fz) local is_safe = true local margin = 0.15 -- 15% отступа от любого ребра/края блока -- Определяем грань попадания и проверяем другие две координаты if dist_x <= dist_y and dist_x <= dist_z then -- Попадание в грань X (проверяем Y и Z) if dist_y < margin or dist_z < margin then is_safe = false end elseif dist_y <= dist_x and dist_y <= dist_z then -- Попадание в грань Y (проверяем X и Z) if dist_x < margin or dist_z < margin then is_safe = false end else -- Попадание в грань Z (проверяем X и Y) if dist_x < margin or dist_y < margin then is_safe = false end end -- Если этого блока еще нет в результатах — создаем его if not blocksMap[key] then local b = { pos = { x = bx, y = by, z = bz }, identifier = block.identifier, angles = {}, -- Все углы попадания (резервный список) safeAngles = {}, -- Углы без задевания краев (основной список) angleDiff = angleDiff, location = loc } blocksMap[key] = b table.insert(blocksList, b) end -- Создаем структуру угла, содержащую также и точные 3D координаты попадания local angleData = { yaw = yaw, pitch = pitch, location = loc } -- Сохраняем ротацию в резервную базу table.insert(blocksMap[key].angles, angleData) -- Если точка безопасна и не находится на краю, сохраняем в основную базу if is_safe then table.insert(blocksMap[key].safeAngles, angleData) end -- Записываем минимальную угловую разницу для сортировки if angleDiff < blocksMap[key].angleDiff then blocksMap[key].angleDiff = angleDiff end end end end end end end -- Переносим углы в финальный рабочий список for _, b in ipairs(blocksList) do -- Если у блока есть безопасные внутренние углы, используем только их. -- Если безопасных углов нет (блок почти полностью закрыт), берем обычные углы в качестве резерва. b.angles = #b.safeAngles > 0 and b.safeAngles or b.angles end -- Сортируем список так, чтобы ближайший к прицелу игрока блок шел первым table.sort(blocksList, function(a, b) return a.angleDiff < b.angleDiff end) return blocksList end -- Обработчик обновления блоков в мире registerBlockUpdate(function(info) if not enabled then return end if currentTarget and info.position then local pos = info.position local ix = math.floor(pos.x) local iy = math.floor(pos.y) local iz = math.floor(pos.z) local tx = currentTarget.pos.x local ty = currentTarget.pos.y local tz = currentTarget.pos.z if ix == tx and iy == ty and iz == tz then local block = world.getBlock(ix, iy, iz) if not block or not targetIdentifiers[block.identifier] then -- Устанавливаем задержку на выбор новой цели от 2 до 4 тиков nextTargetAllowedTick = globalTicks + math.random(2, 4) currentTarget = nil currentTargetTicks = 0 end end end end) registerClientTick(function() if not enabled then return end globalTicks = globalTicks + 1 -- Обновляем список блоков для подсветки каждые 5 тиков (4 раза в секунду), чтобы не просаживать FPS if globalTicks % 5 == 0 then local currentRot = player.getRotation() scannedBlocks = getBlocks(currentRot.yaw, currentRot.pitch) end local eye = player.getEyePosition() local targetValid = false -- 1. Проверяем валидность текущей цели if currentTarget then local pos = currentTarget.pos local block = world.getBlock(pos.x, pos.y, pos.z) if block and block.identifier == currentTarget.identifier then local dx = (pos.x + 0.5) - eye.x local dy = (pos.y + 0.5) - eye.y local dz = (pos.z + 0.5) - eye.z local distSq = dx * dx + dy * dy + dz * dz if distSq <= 5.5 * 5.5 then targetValid = true end end if targetValid then currentTargetTicks = currentTargetTicks + 1 if currentTargetTicks >= 150 then local key = pos.x .. "," .. pos.y .. "," .. pos.z ignoredBlocks[key] = globalTicks + 200 -- черный список на 10 сек targetValid = false end end if not targetValid then nextTargetAllowedTick = globalTicks + math.random(3, 6) currentTarget = nil currentTargetTicks = 0 end end -- 2. Если цели нет и задержка прошла, выбираем новую if not currentTarget and globalTicks >= nextTargetAllowedTick then -- Если список пуст, выполняем принудительное обновление if #scannedBlocks == 0 then local currentRot = player.getRotation() scannedBlocks = getBlocks(currentRot.yaw, currentRot.pitch) end if #scannedBlocks > 0 then currentTarget = scannedBlocks[1] currentTargetTicks = 0 currentTarget.history = {} -- Инициализируем историю посещенных точек на блоке -- Выбираем случайный проверенный угол из тех, что успешно попали в этот блок local randomIndex = math.random(1, #currentTarget.angles) currentTarget.targetRot = currentTarget.angles[randomIndex] table.insert(currentTarget.history, currentTarget.targetRot) -- Запоминаем первую точку -- Запоминаем направление на следующий ближайший блок для пре-эйма (берем случайную точку следующего блока для непредсказуемости) if #scannedBlocks > 1 and #scannedBlocks[2].angles > 0 then local nextRandomIndex = math.random(1, #scannedBlocks[2].angles) currentTarget.nextYaw = scannedBlocks[2].angles[nextRandomIndex].yaw currentTarget.nextPitch = scannedBlocks[2].angles[nextRandomIndex].pitch else currentTarget.nextYaw = nil currentTarget.nextPitch = nil end -- Состояния наведения currentTarget.hasArrived = false currentTarget.nextAimShiftTick = 0 end end -- 3. Наводка через rotations_v2 (Выполняется каждый тик для непрерывности) local targetRot = nil if currentTarget and currentTarget.targetRot then -- Рассчитываем разницу между текущей ротацией игрока и целевой ротацией на блоке local playerRot = player.getRotation() local dyaw = currentTarget.targetRot.yaw - playerRot.yaw if dyaw > 180 then dyaw = dyaw - 360 end if dyaw < -180 then dyaw = dyaw + 360 end local dpitch = currentTarget.targetRot.pitch - playerRot.pitch local diffSq = dyaw * dyaw + dpitch * dpitch -- Если мы еще не довели прицел до выбранной точки if not currentTarget.hasArrived then -- Если погрешность наводки меньше 0.6 градуса — мы успешно навелись if diffSq < 0.36 then currentTarget.hasArrived = true -- Назначаем случайную задержку в тиках перед выбором следующей точки currentTarget.nextAimShiftTick = globalTicks + math.random(minShiftDelay, maxShiftDelay) end else -- Если мы уже навелись и стоим на точке, ждем окончания случайной задержки if globalTicks >= currentTarget.nextAimShiftTick then -- Меняем точку, только если для этого блока найдено больше одного безопасного угла if #currentTarget.angles > 1 then -- Составляем список альтернативных углов (исключая те, что уже есть в истории) local candidates = {} for _, ang in ipairs(currentTarget.angles) do local visited = false for _, hist in ipairs(currentTarget.history) do if math.abs(hist.yaw - ang.yaw) < 0.01 and math.abs(hist.pitch - ang.pitch) < 0.01 then visited = true break end end if not visited then table.insert(candidates, ang) end end -- Если все доступные точки уже были посещены в этом цикле, сбрасываем историю if #candidates == 0 then currentTarget.history = { { yaw = currentTarget.targetRot.yaw, pitch = currentTarget.targetRot.pitch } } for _, ang in ipairs(currentTarget.angles) do if math.abs(ang.yaw - currentTarget.targetRot.yaw) >= 0.01 or math.abs(ang.pitch - currentTarget.targetRot.pitch) >= 0.01 then table.insert(candidates, ang) end end end -- Выбираем новую точку из альтернативных кандидатов if #candidates > 0 then local selectedAng = nil -- Если у нас есть информация о следующем блоке, выбираем точку ближе к нему if currentTarget.nextYaw and currentTarget.nextPitch then local bestAng = nil local minDiff = 999999 for _, ang in ipairs(candidates) do local dyawDiff = ang.yaw - currentTarget.nextYaw if dyawDiff > 180 then dyawDiff = dyawDiff - 360 end if dyawDiff < -180 then dyawDiff = dyawDiff + 360 end local dpitchDiff = ang.pitch - currentTarget.nextPitch if dpitchDiff > 180 then dpitchDiff = dpitchDiff - 360 end if dpitchDiff < -180 then dpitchDiff = dpitchDiff + 360 end local diff = dyawDiff * dyawDiff + dpitchDiff * dpitchDiff if diff < minDiff then minDiff = diff bestAng = ang end end selectedAng = bestAng else -- Если других блоков нет, выбираем случайно selectedAng = candidates[math.random(1, #candidates)] end if selectedAng then currentTarget.targetRot = selectedAng table.insert(currentTarget.history, selectedAng) -- Записываем точку в историю end end -- Сбрасываем флаг наведения, так как прицел начал движение к новой точке currentTarget.hasArrived = false end end end targetRot = currentTarget.targetRot lastActiveRot = { yaw = targetRot.yaw, pitch = targetRot.pitch } elseif lastActiveRot then -- Если цели нет (задержка между блоками), продолжаем удерживать последнюю ротацию targetRot = lastActiveRot end -- Отправляем ротацию в библиотеку без пропусков тиков if targetRot then rotations.rotateToYawPitch(targetRot.yaw, targetRot.pitch) end -- 4. Защита взгляда (определяем, стоит ли нажимать кнопку атаки на этом тике) local isAttacking = false if currentTarget then local currentRot = player.getRotation() local ray = player.raycast(4.5) if ray and ray.type == "block" and ray.blockPos then isAttacking = true end if ray and ray.type == "entity" then isAttacking = false currentTarget = nil end end local entities = world.getLivingEntities() for index, entity in ipairs(entities) do if entity ~= player.entity and entity.type == "entity.minecraft.player" then if player.entity.box.intersects(entity.box) then isAttacking = false break end end end player.input.setPressedAttack(false) if not player.inventory.isAnyScreenOpened() then player.input.setPressedAttack(isAttacking) end -- 5. Предсказание следующей точки для отрисовки желтого квадратика local function predictNext() if not currentTarget then if #scannedBlocks > 1 and #scannedBlocks[2].angles > 0 then return scannedBlocks[2].angles[1] end return nil end -- Если у текущего блока несколько углов, прицел сначала пойдет по ним if #currentTarget.angles > 1 then local candidates = {} for _, ang in ipairs(currentTarget.angles) do local visited = false for _, hist in ipairs(currentTarget.history or {}) do if math.abs(hist.yaw - ang.yaw) < 0.01 and math.abs(hist.pitch - ang.pitch) < 0.01 then visited = true break end end if not visited then table.insert(candidates, ang) end end if #candidates == 0 then for _, ang in ipairs(currentTarget.angles) do if math.abs(ang.yaw - currentTarget.targetRot.yaw) >= 0.01 or math.abs(ang.pitch - currentTarget.targetRot.pitch) >= 0.01 then table.insert(candidates, ang) end end end if #candidates > 0 then if currentTarget.nextYaw and currentTarget.nextPitch then local bestAng = nil local minDiff = 999999 for _, ang in ipairs(candidates) do local dyawDiff = ang.yaw - currentTarget.nextYaw if dyawDiff > 180 then dyawDiff = dyawDiff - 360 end if dyawDiff < -180 then dyawDiff = dyawDiff + 360 end local dpitchDiff = ang.pitch - currentTarget.nextPitch if dpitchDiff > 180 then dpitchDiff = dpitchDiff - 360 end if dpitchDiff < -180 then dpitchDiff = dpitchDiff + 360 end local diff = dyawDiff * dyawDiff + dpitchDiff * dpitchDiff if diff < minDiff then minDiff = diff bestAng = ang end end return bestAng else return candidates[1] end end end -- Если у текущего блока остался всего 1 угол, то берем следующий блок for i = 1, #scannedBlocks do local b = scannedBlocks[i] if b.pos.x ~= currentTarget.pos.x or b.pos.y ~= currentTarget.pos.y or b.pos.z ~= currentTarget.pos.z then if #b.angles > 0 then return b.angles[1] end end end return nil end nextTargetVisual = predictNext() end) registerWorldRenderer(function(ctx) if not enabled then return end rotations.update() local eye = player.getEyePosition() local currentRot = player.getRotation() -- 1. Подсветка текущей цели красным цветом if currentTarget and currentTarget.targetRot and currentTarget.targetRot.location then local targetLoc = currentTarget.targetRot.location local offset = 0.05 local box = creator.createBox( targetLoc.x - offset, targetLoc.y - offset, targetLoc.z - offset, targetLoc.x + offset, targetLoc.y + offset, targetLoc.z + offset ) ctx.renderFilled(box, 255, 0, 0, 140, true) end -- 2. Подсветка следующей цели желтым цветом if nextTargetVisual and nextTargetVisual.location then local targetLoc = nextTargetVisual.location local offset = 0.05 local box = creator.createBox( targetLoc.x - offset, targetLoc.y - offset, targetLoc.z - offset, targetLoc.x + offset, targetLoc.y + offset, targetLoc.z + offset ) ctx.renderFilled(box, 255, 255, 0, 140, true) end -- 3. Подсветка всех найденных блоков белым цветом local margin = 0.002 for _, b in ipairs(scannedBlocks) do local bx = b.pos.x local by = b.pos.y local bz = b.pos.z local box = creator.createBox( bx - margin, by - margin, bz - margin, bx + 1 + margin, by + 1 + margin, bz + 1 + margin ) ctx.renderFilled(box, 0, 0, 255, 80, false) end end) registerKeyEvent(function(key, action) if key == keys.KEY_GRAVE_ACCENT and action == "Press" then enabled = not enabled if enabled then player.addMessage("Enabled") else player.addMessage("Disabled") player.input.setPressedAttack(false) currentTarget = nil targetRot = nil end end end)