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 = 1 local minShiftDelay = 10 local maxShiftDelay = 15 -- НАСТРОЙКИ РЕСПАВНА И ОЖИДАНИЯ local defaultRespawnDelayTicks = 300 -- Время респавна мифрила на Hypixel (15 секунд / 300 тиков) local maxWaitDistance = 5.5 -- Максимальная дистанция (в блоках) от игрока до руды для ожидания респавна -- Базовый список всех валидных блоков 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, ["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, } -- Группировка: связываем разные блоки в один виртуальный тип руды local blockToGroup = { ["minecraft:cyan_terracotta"] = "mithril", ["minecraft:light_blue_wool"] = "mithril", ["minecraft:polished_diorite"] = "mithril", ["minecraft:dark_prismarine"] = "mithril", ["minecraft:gray_wool"] = "mithril", ["minecraft:prismarine"] = "mithril", ["minecraft:prismarine_bricks"] = "mithril", } -- Функция для получения "семейства" блока (группа или собственный ID) local function getBlockGroup(identifier) if not identifier then return nil end return blockToGroup[identifier] or identifier end -- Переменные состояния local currentTarget = nil local currentTargetTicks = 0 local lastActiveRot = nil local ignoredBlocks = {} local globalTicks = 0 local nextTargetAllowedTick = 0 local scannedBlocks = {} local nextTargetVisual = nil local switchTargetDelay = 0 -- ГЛОБАЛЬНЫЙ ТРЭКИНГ ВРЕМЕНИ И TPS local serverGameTime = 0 local lastServerTimeUpdate = 0 local currentTPS = 20.0 -- ТАБЛИЦЫ ДЛЯ АВТО-РЕСПАВНА И СТАТИСТИКИ local brokenBlocks = {} local respawnDelays = {} local activeBreaks = {} -- Переменные для глобального кеширования геометрии local lastCachedPlayerPos = { x = 0, y = 0, z = 0 } local cachedWorldMap = nil local cachedBlocksList = nil -- Текущая доминантная группа и последняя валидная группа local dominantGroup = nil local lastValidDominantGroup = nil registerServerSetTimeEvent(function(dayTime, gameTime, tickDayTime) local currentTime = os.clock() if serverGameTime > 0 and lastServerTimeUpdate > 0 then local tickDiff = gameTime - serverGameTime local timeDiff = currentTime - lastServerTimeUpdate if timeDiff > 0 and tickDiff > 0 then currentTPS = tickDiff / timeDiff if currentTPS > 20.0 then currentTPS = 20.0 end end end serverGameTime = gameTime lastServerTimeUpdate = currentTime end) 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) lastActiveRot = nil end) -- Основная функция сканирования и вычисления доминантной группы local function updateWorldAndDominance(currentYaw, currentPitch) local playerPos = player.getPos() local px, py, pz = math.floor(playerPos.x), math.floor(playerPos.y), math.floor(playerPos.z) if not cachedWorldMap or lastCachedPlayerPos.x ~= px or lastCachedPlayerPos.y ~= py or lastCachedPlayerPos.z ~= pz then lastCachedPlayerPos.x = px lastCachedPlayerPos.y = py lastCachedPlayerPos.z = pz cachedWorldMap = {} local headPos = player.getEyePosition() for yaw = -180, 180, step do for pitch = 90, -90, -step do local result = world.raycastFromRotation({ 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 if result.type == "entity" then goto continue end local blockPos = result.blockPos local block = world.getBlock(blockPos.x, blockPos.y, blockPos.z) if block and block.identifier then local bx, by, bz = math.floor(blockPos.x), math.floor(blockPos.y), math.floor(blockPos.z) local key = bx .. "," .. by .. "," .. bz 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, fy, fz = loc.x - bx, loc.y - by, 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 if dist_x <= dist_y and dist_x <= dist_z then if dist_y < margin or dist_z < margin then is_safe = false end elseif dist_y <= dist_x and dist_y <= dist_z then if dist_x < margin or dist_z < margin then is_safe = false end else if dist_x < margin or dist_y < margin then is_safe = false end end if not cachedWorldMap[key] then cachedWorldMap[key] = { pos = { x = bx, y = by, z = bz }, identifier = block.identifier, angles = {}, safeAngles = {}, angleDiff = angleDiff, location = loc } end local angleData = { yaw = yaw, pitch = pitch, location = loc } table.insert(cachedWorldMap[key].angles, angleData) if is_safe then table.insert(cachedWorldMap[key].safeAngles, angleData) end if angleDiff < cachedWorldMap[key].angleDiff then cachedWorldMap[key].angleDiff = angleDiff end end end ::continue:: end end end local totalCounts = {} local aliveTargetsMap = {} for key, b in pairs(cachedWorldMap) do if not ignoredBlocks[key] or globalTicks >= ignoredBlocks[key] then local currentBlock = world.getBlock(b.pos.x, b.pos.y, b.pos.z) local currentId = nil if currentBlock and targetIdentifiers[currentBlock.identifier] then currentId = currentBlock.identifier b.identifier = currentId aliveTargetsMap[key] = b -- Если блок снова появился (живой), удаляем его из списка сломанных if brokenBlocks[key] then brokenBlocks[key] = nil end elseif brokenBlocks[key] then currentId = brokenBlocks[key].identifier end if currentId and targetIdentifiers[currentId] then local group = getBlockGroup(currentId) totalCounts[group] = (totalCounts[group] or 0) + 1 end end end local maxCount = 0 local bestGroup = nil for group, count in pairs(totalCounts) do if count > maxCount then maxCount = count bestGroup = group end end if bestGroup ~= dominantGroup and bestGroup ~= nil then dominantGroup = bestGroup lastValidDominantGroup = bestGroup local cleanName = dominantGroup:gsub("minecraft:", "") player.addMessage("🎯 Приоритет группы: " .. cleanName .. " (" .. maxCount .. " шт.)") elseif bestGroup == nil then dominantGroup = nil end local targetGroup = dominantGroup or lastValidDominantGroup local filteredLiveList = {} if targetGroup then for key, b in pairs(aliveTargetsMap) do if getBlockGroup(b.identifier) == targetGroup then local minDiff = 999999 for _, ang in ipairs(b.angles) do local dyaw = ang.yaw - currentYaw if dyaw > 180 then dyaw = dyaw - 360 end if dyaw < -180 then dyaw = dyaw + 360 end local dpitch = ang.pitch - currentPitch local diff = dyaw * dyaw + dpitch * dpitch if diff < minDiff then minDiff = diff end end b.angleDiff = minDiff table.insert(filteredLiveList, b) end end end table.sort(filteredLiveList, function(a, b) return a.angleDiff < b.angleDiff end) cachedBlocksList = filteredLiveList return cachedBlocksList end -- ОБНОВЛЕННЫЙ БЛОК ОБРАБОТКИ СОБЫТИЙ МИРА registerBlockUpdate(function(info) if not enabled then return end local pos = info.position local key = pos.x .. "," .. pos.y .. "," .. pos.z -- Проверяем, есть ли этот конкретный изменившийся блок в нашем кэше геометрии if cachedWorldMap and cachedWorldMap[key] then local currentBlock = world.getBlock(pos.x, pos.y, pos.z) if currentBlock then if targetIdentifiers[currentBlock.identifier] then -- Блок зареспавнился! Гарантированно сбрасываем его из списка сломанных if brokenBlocks[key] then brokenBlocks[key] = nil end else -- Блок сломан! Перезаписываем запись свежим таймером респавна local currentTick = (serverGameTime and serverGameTime > 0) and serverGameTime or globalTicks local anglesList = cachedWorldMap[key].angles brokenBlocks[key] = { respawnAtServerTick = currentTick + defaultRespawnDelayTicks, targetRot = anglesList[math.random(1, #anglesList)], pos = { x = pos.x, y = pos.y, z = pos.z }, identifier = cachedWorldMap[key].identifier } end end end end) registerClientTick(function() if not enabled then return end globalTicks = globalTicks + 1 if serverGameTime > 0 then serverGameTime = serverGameTime + 1 end if globalTicks % 2 == 0 then local currentRot = player.getRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) end local eye = player.getEyePosition() local targetValid = false if currentTarget then local pos = currentTarget.pos local block = world.getBlock(pos.x, pos.y, pos.z) if currentTarget.isWaiting then if block and targetIdentifiers[block.identifier] then -- Блок, который мы ждали, зареспавнился! -- Бесшовно переводим его в статус обычной цели без сброса и изменения положения камеры currentTarget.isWaiting = false currentTarget.identifier = block.identifier currentTarget.hasArrived = true currentTargetTicks = 0 end targetValid = (currentTarget ~= nil) else local activeDominantGroup = dominantGroup or lastValidDominantGroup -- ИСПРАВЛЕНИЕ: Облегченная и безошибочная проверка валидности цели на основе группы. -- Это полностью решает проблему, когда блок меняет цвет в момент респавна. if block and getBlockGroup(block.identifier) == activeDominantGroup and targetIdentifiers[block.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 else targetValid = false end if targetValid then currentTargetTicks = currentTargetTicks + 1 if currentTargetTicks >= 150 then local key = pos.x .. "," .. pos.y .. "," .. pos.z ignoredBlocks[key] = globalTicks + 200 targetValid = false end end if not targetValid then nextTargetAllowedTick = globalTicks + 2 currentTarget = nil currentTargetTicks = 0 end end end if currentTarget and currentTarget.isWaiting then local foundBetter = false -- Ищем, есть ли что-то доступное для добычи прямо сейчас for _, b in ipairs(scannedBlocks) do if getBlockGroup(b.identifier) == getBlockGroup(currentTarget.identifier) then local block = world.getBlock(b.pos.x, b.pos.y, b.pos.z) if block and targetIdentifiers[block.identifier] then foundBetter = true break end end end if foundBetter then if switchTargetDelay <= 0 then -- Время вышло, начинаем переключение switchTargetDelay = math.random(7, 11) -- Устанавливаем задержку elseif switchTargetDelay > 1 then -- Тикаем до нуля switchTargetDelay = switchTargetDelay - 1 else -- Задержка закончилась, сбрасываем цель currentTarget = nil switchTargetDelay = 0 end else -- Если блок исчез из зоны видимости или его больше нет, сбрасываем счетчик switchTargetDelay = 0 end end -- Если текущая цель была сброшена (или ее не было), берем новую из списка if not currentTarget and globalTicks >= nextTargetAllowedTick then if #scannedBlocks == 0 then local currentRot = player.getRotation() scannedBlocks = updateWorldAndDominance(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 else local bestPrediction = nil local minTicksLeft = 999999 -- Ищем самый ранний сломанный блок (наименьшее время ожидания) local activeGroup = dominantGroup or lastValidDominantGroup local currentTick = (serverGameTime and serverGameTime > 0) and serverGameTime or globalTicks if activeGroup then for key, data in pairs(brokenBlocks) do if getBlockGroup(data.identifier) == activeGroup then local ticksLeft = data.respawnAtServerTick - currentTick if ticksLeft < -100 then brokenBlocks[key] = nil else local dx = (data.pos.x + 0.5) - eye.x local dy = (data.pos.y + 0.5) - eye.y local dz = (data.pos.z + 0.5) - eye.z local distSq = dx * dx + dy * dy + dz * dz -- 1. Фильтр: Блок ДОЛЖЕН быть рядом с игроком в пределах maxWaitDistance if distSq <= maxWaitDistance * maxWaitDistance then -- 2. Сортировка: Выбираем из ближних тот, который сломали раньше всех (ticksLeft минимальный) if ticksLeft < minTicksLeft then minTicksLeft = ticksLeft bestPrediction = data end end end end end end if bestPrediction then local key = bestPrediction.pos.x .. "," .. bestPrediction.pos.y .. "," .. bestPrediction.pos.z local chosenRot = bestPrediction.targetRot if cachedWorldMap and cachedWorldMap[key] and cachedWorldMap[key].angles and #cachedWorldMap[key].angles > 0 then local angles = cachedWorldMap[key].angles chosenRot = angles[math.random(1, #angles)] end currentTarget = { pos = bestPrediction.pos, targetRot = chosenRot, identifier = bestPrediction.identifier, isWaiting = true, hasArrived = false } end end end local targetRot = nil local shouldRotate = true if currentTarget and currentTarget.targetRot then if not currentTarget.isWaiting then local freshRay = world.raycastFromRotation({ startX = eye.x, startY = eye.y, startZ = eye.z, yaw = currentTarget.targetRot.yaw, pitch = currentTarget.targetRot.pitch, range = 4.5, include_fluid = false, include_entity = true }) if freshRay and freshRay.type == "entity" then shouldRotate = false currentTarget = nil end end if shouldRotate 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 if diffSq < 0.36 then currentTarget.hasArrived = true currentTarget.nextAimShiftTick = globalTicks + math.random(minShiftDelay, maxShiftDelay) end elseif not currentTarget.isWaiting then 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 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 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 } end elseif lastActiveRot then targetRot = lastActiveRot end if targetRot and shouldRotate then rotations.rotateToYawPitch(targetRot.yaw, targetRot.pitch) end local isAttacking = false if currentTarget and shouldRotate and not currentTarget.isWaiting then local ray = player.raycast(4.5) if ray then if ray.type == "block" and ray.blockPos then isAttacking = true elseif ray.type == "entity" then isAttacking = false currentTarget = nil end end end local entities = world.getLivingEntities() for _, 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 local function predictNext() if not currentTarget or currentTarget.isWaiting then 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 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() 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 ) if currentTarget.isWaiting then ctx.renderFilled(box, 255, 0, 255, 140, true) else ctx.renderFilled(box, 255, 0, 0, 140, true) end end 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 local margin = 0.002 for _, b in ipairs(scannedBlocks) do local box = creator.createBox( b.pos.x - margin, b.pos.y - margin, b.pos.z - margin, b.pos.x + 1 + margin, b.pos.y + 1 + margin, b.pos.z + 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") lastCachedPlayerPos = { x = 0, y = 0, z = 0 } serverGameTime = 0 lastServerTimeUpdate = 0 player.input.setPressedAttack(false) currentTarget = nil targetRot = nil cachedWorldMap = nil cachedBlocksList = nil lastActiveRot = nil brokenBlocks = {} activeBreaks = {} dominantGroup = nil lastValidDominantGroup = nil end end end)