local player = require("player") local world = require("world") local creator = require("creator") local keys = require("keys") local rotations = require("silent_rotations_v2") rotations.setRotationSpeed(13) local enabled = false local step = 1.37 local pickobulusStep = 10 local minShiftDelay = 10 local maxShiftDelay = 15 -- НАСТРОЙКИ ДИСТАНЦИИ И ОТСТУПОВ local maxMiningReach = 4.47 -- Максимальная дистанция копания (все точки дальше будут отсекаться) local edgeMargin = 0.15 -- Минимальный отступ от края блока (0.15 = 15% от ширины грани) -- НАСТРОЙКИ РЕСПАВНА И ОЖИДАНИЯ local defaultRespawnDelayTicks = 300 -- Время респавна мифрила на Hypixel (15 секунд / 300 тиков) local maxWaitDistance = 3.0 -- Дистанция от сломанного блока до новой живой руды для переключения приоритета -- НАСТРОЙКИ ПИКОБОЛУСА (PICKOBULUS) local pickobulusEnabled = true local pickobulusIntervalTicks = 1200 -- Интервал использования в тиках (1200 тиков = 60 секунд) local minPickobulusDistance = 8.0 -- Минимальная дистанция броска local maxPickobulusDistance = 15.0 -- Максимальная дистанция броска local pickobulusFovLimit = 40.0 -- Угол обзора (в градусах), внутри которого приоритетно ищется лучшая цель -- Базовый список всех валидных блоков 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", } -- Список приоритетов внутри mithril (чем меньше число, тем выше приоритет) local mithrilPriority = { ["minecraft:polished_diorite"] = 1, ["minecraft:light_blue_wool"] = 2, ["minecraft:prismarine"] = 3, ["minecraft:dark_prismarine"] = 4, ["minecraft:gray_wool"] = 5, ["minecraft:cyan_terracotta"] = 6, ["minecraft:prismarine_bricks"] = 7, } -- Вспомогательная функция для удаления цветовых кодов майнкрафта (§a, §7 и т.д.) local function stripColorCodes(str) if not str then return "" end return str:gsub("§.", "") end -- Вспомогательная функция для получения текущего направления взгляда (с поддержкой silent_rotations_v2) local function getCurrentRotation() if rotations.getRotation then local r = rotations.getRotation() if r and r.yaw and r.pitch then return r end end if rotations.yaw and rotations.pitch then return { yaw = rotations.yaw, pitch = rotations.pitch } end if rotations.getYaw and rotations.getPitch then return { yaw = rotations.getYaw(), pitch = rotations.getPitch() } end if player.getSilentRotation then local r = player.getSilentRotation() if r and r.yaw and r.pitch then return r end end return player.getRotation() end -- Функция для получения "семейства" блока (группа или собственный 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 switchTargetDelay = 0 local lastBrokenBlockPos = nil -- Координаты последнего успешно вскопанного блока -- Состояние Пикоболуса local lastPickobulusTick = 0 local pickobulusState = nil -- Может быть "aiming" или nil local pickobulusTarget = nil local pickobulusAimTicks = 0 local pickobulusTriggerPending = false local pickobulusDelayTicksLeft = -1 -- КЭШ ПИКОБОЛУСА local lastPickobulusPlayerPos = { x = 0, y = 0, z = 0 } local cachedPickobulusTargets = nil -- ГЛОБАЛЬНЫЙ ТРЭКИНГ ВРЕМЕНИ И 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 = getCurrentRotation() if rot.yaw == yaw and rot.pitch == pitch then return end enabled = false player.addMessage("§7[§6Hypixel Cry§7] §cYou 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) local headPos = player.getEyePosition() -- Опорная точка: последний вскопанный блок, либо глаза игрока (если еще ничего не копали) local refPos = lastBrokenBlockPos or headPos 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 = {} 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 = maxMiningReach, include_fluid = false, include_entity = true, }) if result and result.blockPos and result.location then if result.type == "entity" then goto continue end local loc = result.location -- Проверка точной евклидовой дистанции до точки попадания луча local dx = loc.x - headPos.x local dy = loc.y - headPos.y local dz = loc.z - headPos.z local exactDist = math.sqrt(dx * dx + dy * dy + dz * dz) if exactDist > maxMiningReach then goto continue end -- Исключаем точки дальше 4.47 блоков 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 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 if dist_x <= dist_y and dist_x <= dist_z then if dist_y < edgeMargin or dist_z < edgeMargin then is_safe = false end elseif dist_y <= dist_x and dist_y <= dist_z then if dist_x < edgeMargin or dist_z < edgeMargin then is_safe = false end else if dist_x < edgeMargin or dist_y < edgeMargin then is_safe = false end end -- Исключаем точки, которые находятся слишком близко к краю блока if not is_safe then goto continue 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) table.insert(cachedWorldMap[key].safeAngles, angleData) 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("§7[§6Hypixel Cry§7] §a🎯 Group priority: " .. cleanName .. " (" .. maxCount .. " pcs.)") 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 local hasReach = false -- Проверяем досягаемость хотя бы одной точки наведения с текущей позиции for _, ang in ipairs(b.angles) do local adx = ang.location.x - headPos.x local ady = ang.location.y - headPos.y local adz = ang.location.z - headPos.z local curDistSq = adx * adx + ady * ady + adz * adz if curDistSq <= maxMiningReach * maxMiningReach then hasReach = true 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 end -- Если ни один угол до блока не достает, исключаем его из списка копания if hasReach then b.angleDiff = minDiff -- Вычисляем расстояние до ОПОРНОЙ ТОЧКИ (последнего вскопанного блока или глаз игрока) local dx = (b.pos.x + 0.5) - refPos.x local dy = (b.pos.y + 0.5) - refPos.y local dz = (b.pos.z + 0.5) - refPos.z b.distSq = dx * dx + dy * dy + dz * dz -- Подтягиваем приоритет внутри группы b.priority = mithrilPriority[b.identifier] or 999 table.insert(filteredLiveList, b) end end end end table.sort(filteredLiveList, function(a, b) if targetGroup == "mithril" then -- Сортировка по приоритету блока, а при равных значениях — по расстоянию до вскопанного блока if a.priority ~= b.priority then return a.priority < b.priority else return a.distSq < b.distSq end else -- Стандартная сортировка для иных руд по углу отклонения return a.angleDiff < b.angleDiff end end) cachedBlocksList = filteredLiveList return cachedBlocksList end -- ВЫДЕЛЕННЫЙ СКАНЕР ПИКОБОЛУСА (ПЕРВИЧНЫЙ СБОР ТВЕРДЫХ БЛОКОВ С КОЛЛИЗИЕЙ) local function scanPickobulusSpace(playerPos) local headPos = player.getEyePosition() local rawHits = {} local scanStep = pickobulusStep -- Сканируем окружение рейкастом for yaw = -180, 180, scanStep do for pitch = 90, -90, -scanStep do local result = world.raycastFromRotation({ startX = headPos.x, startY = headPos.y, startZ = headPos.z, yaw = yaw, pitch = pitch, range = maxPickobulusDistance, include_fluid = false, include_entity = true, }) -- Проверяем, что луч действительно уперся в твердый блок (коллизия взрыва) if result and result.blockPos and result.type == "block" then local bx, by, bz = math.floor(result.blockPos.x), math.floor(result.blockPos.y), math.floor(result.blockPos.z) local key = bx .. "," .. by .. "," .. bz if not rawHits[key] then local dx = (bx + 0.5) - playerPos.x local dy = (by + 0.5) - playerPos.y local dz = (bz + 0.5) - playerPos.z local dist = math.sqrt(dx * dx + dy * dy + dz * dz) -- Фильтрация по дистанции if dist >= minPickobulusDistance and dist <= maxPickobulusDistance then rawHits[key] = { pos = { x = bx, y = by, z = bz }, angles = { { yaw = yaw, pitch = pitch, location = result.location } }, dist = dist } end end end end end -- Собираем список всех валидных точек столкновения local targetList = {} for _, b in pairs(rawHits) do table.insert(targetList, b) end return targetList end -- БЫСТРЫЙ ДИНАМИЧЕСКИЙ ВЫБОР ЛУЧШЕЙ ТОЧКИ ИЗ КЭША (С ПРИОРИТЕТОМ НАПРАВЛЕНИЯ ВЗГЛЯДА И ТИПА РУДЫ) local function findBestPickobulusTargetFromCache() if not cachedPickobulusTargets or #cachedPickobulusTargets == 0 then return nil end local rot = getCurrentRotation() local activeGroup = dominantGroup or lastValidDominantGroup -- Получаем текущую активную группу копания -- Генерация 123 офсетов сферы радиусом 3.0 блока вокруг точки попадания local sphereOffsets = {} for x = -3, 3 do for y = -3, 3 do for z = -3, 3 do if x*x + y*y + z*z <= 9 then table.insert(sphereOffsets, { x = x, y = y, z = z }) end end end end local bestBlock = nil local maxGlobalScore = -999999 local bestBlockInFov = nil local maxScoreInFov = -999999 local fovLimitSq = pickobulusFovLimit * pickobulusFovLimit -- Оцениваем текущее количество живых руд возле каждой кэшированной точки коллизии for _, b in ipairs(cachedPickobulusTargets) do local density = 0 for _, offset in ipairs(sphereOffsets) do local ox, oy, oz = b.pos.x + offset.x, b.pos.y + offset.y, b.pos.z + offset.z local block = world.getBlock(ox, oy, oz) if block and targetIdentifiers[block.identifier] then local blockGroup = getBlockGroup(block.identifier) -- Считаем плотность только для той группы блоков, которую сейчас копаем if not activeGroup or blockGroup == activeGroup then density = density + 1 end end end -- Если нашли руды в радиусе взрыва if density > 0 then b.density = density -- Проверяем, является ли сам блок столкновения нашей целевой рудой local hitBlock = world.getBlock(b.pos.x, b.pos.y, b.pos.z) local isTargetBlock = false if hitBlock and targetIdentifiers[hitBlock.identifier] then local hitGroup = getBlockGroup(hitBlock.identifier) if not activeGroup or hitGroup == activeGroup then isTargetBlock = true end end -- Рассчитываем оценку (score) цели: -- 1. Высокая плотность руд дает огромный плюс (+20 за каждый блок). -- 2. Расстояние штрафует оценку (-1 за каждый блок дистанции), чтобы предпочесть более близкие кучки. -- 3. Прямое попадание в саму руду (а не в камень рядом) дает бонус (+15), направляя взрыв точно в центр. local score = density * 20 - b.dist if isTargetBlock then score = score + 15 end b.score = score -- Вычисляем угловое отклонение от текущего направления взгляда игрока local dyaw = b.angles[1].yaw - rot.yaw if dyaw > 180 then dyaw = dyaw - 360 end if dyaw < -180 then dyaw = dyaw + 360 end local dpitch = b.angles[1].pitch - rot.pitch local angleDiff = dyaw * dyaw + dpitch * dpitch b.angleDiff = angleDiff -- Вариант 1: Цель находится в пределах желаемого конуса обзора (FOV) if angleDiff <= fovLimitSq then if score > maxScoreInFov then maxScoreInFov = score bestBlockInFov = b elseif score == maxScoreInFov then -- При равной оценке выбираем ту, которая ближе к центру экрана if angleDiff < (bestBlockInFov and bestBlockInFov.angleDiff or 999999) then bestBlockInFov = b end end end -- Вариант 2: Глобальный поиск (на случай, если в FOV ничего нет) if score > maxGlobalScore then maxGlobalScore = score bestBlock = b end end end local selectedBlock = bestBlockInFov or bestBlock if not selectedBlock then player.addMessage("§7[§6Hypixel Cry§7] §c❌ Error: No live target ore found within 3 blocks around cached collision points!") end return selectedBlock 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 = info.new 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 -- Проверяем, что в списке есть доступные безопасные углы local targetRotVal = nil if anglesList and #anglesList > 0 then targetRotVal = anglesList[math.random(1, #anglesList)] end brokenBlocks[key] = { respawnAtServerTick = currentTick + defaultRespawnDelayTicks, targetRot = targetRotVal, pos = { x = pos.x, y = pos.y, z = pos.z }, identifier = cachedWorldMap[key].identifier } -- Если текущий разрушенный блок совпадает с нашей активной целью, запускаем задержку if currentTarget and not currentTarget.isWaiting then if pos.x == currentTarget.pos.x and pos.y == currentTarget.pos.y and pos.z == currentTarget.pos.z then -- Запоминаем позицию последнего успешно вскопанного нами блока lastBrokenBlockPos = { x = pos.x, y = pos.y, z = pos.z } if pickobulusTriggerPending then pickobulusTriggerPending = false pickobulusDelayTicksLeft = math.random(3, 6) end end end end end end end) registerClientTick(function() if not enabled then return end if player.inventory.isAnyScreenOpened() then return end rotations.update() globalTicks = globalTicks + 1 if serverGameTime > 0 then serverGameTime = serverGameTime + 1 end local playerPos = player.getPos() local px, py, pz = math.floor(playerPos.x), math.floor(playerPos.y), math.floor(playerPos.z) -- Кэширование геометрии для Пикоболуса (выполняется строго при перемещении игрока) if not cachedPickobulusTargets or px ~= lastPickobulusPlayerPos.x or py ~= lastPickobulusPlayerPos.y or pz ~= lastPickobulusPlayerPos.z then lastPickobulusPlayerPos.x = px lastPickobulusPlayerPos.y = py lastPickobulusPlayerPos.z = pz cachedPickobulusTargets = scanPickobulusSpace(playerPos) end if globalTicks % 2 == 0 then local currentRot = getCurrentRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) end local eye = player.getEyePosition() local targetValid = false -- 1. Логика инициации Пикоболуса if pickobulusEnabled and (globalTicks - lastPickobulusTick >= pickobulusIntervalTicks) and not pickobulusState then if not pickobulusTriggerPending and pickobulusDelayTicksLeft == -1 then -- Если мы сейчас активно ломаем блок, заставляем дождаться окончания копки if currentTarget and not currentTarget.isWaiting then pickobulusTriggerPending = true else -- Если мы свободны, сразу запускаем задержку в 3-6 тиков pickobulusDelayTicksLeft = math.random(3, 6) end end end -- Обработка задержки перед броском if pickobulusDelayTicksLeft > 0 then pickobulusDelayTicksLeft = pickobulusDelayTicksLeft - 1 if pickobulusDelayTicksLeft == 0 then pickobulusDelayTicksLeft = -1 local bestP = findBestPickobulusTargetFromCache() if bestP and bestP.angles and #bestP.angles > 0 then pickobulusState = "aiming" pickobulusTarget = bestP pickobulusAimTicks = 0 -- Расчет случайного смещения от 3 до 10 градусов для броска local signYaw = (math.random(0, 1) == 0) and 1 or -1 local signPitch = (math.random(0, 1) == 0) and 1 or -1 pickobulusTarget.yawOffset = signYaw * (math.random(100, 400) / 100) pickobulusTarget.pitchOffset = signPitch * (math.random(100, 300) / 100) player.addMessage("§7[§6Hypixel Cry§7] §6☄ Alignment with a collision block with offset: " .. string.format("%.2f", pickobulusTarget.yawOffset) .. "° / " .. string.format("%.2f", pickobulusTarget.pitchOffset) .. "° (Near the ore deposits: " .. bestP.density .. " pcs.)") else -- Если подходящей цели не нашли, откладываем попытку на 3 секунды (60 тиков) lastPickobulusTick = globalTicks - pickobulusIntervalTicks + 60 end end end -- Предохранитель: если мы ждем разрушения блока, но цель была сброшена/потеряна if pickobulusTriggerPending and (not currentTarget or currentTarget.isWaiting) then pickobulusTriggerPending = false pickobulusDelayTicksLeft = math.random(3, 6) end -- Стандартная проверка валидности обычной цели if currentTarget and not pickobulusState 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.nextAimShiftTick = globalTicks + math.random(minShiftDelay, maxShiftDelay) currentTargetTicks = 0 end -- Проверяем дистанцию до ожидания по точке наведения local targetLoc = currentTarget.targetRot and currentTarget.targetRot.location if targetLoc then local dx = targetLoc.x - eye.x local dy = targetLoc.y - eye.y local dz = targetLoc.z - eye.z local distSq = dx * dx + dy * dy + dz * dz targetValid = (distSq <= maxMiningReach * maxMiningReach) else 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 targetValid = (distSq <= maxMiningReach * maxMiningReach) end else local activeDominantGroup = dominantGroup or lastValidDominantGroup local isGroupMatch = (activeDominantGroup == nil) or (getBlockGroup(block.identifier) == activeDominantGroup) if block and isGroupMatch and targetIdentifiers[block.identifier] then -- Проверяем точную дистанцию до текущей выбранной точки наведения на блоке local targetLoc = currentTarget.targetRot and currentTarget.targetRot.location if targetLoc then local dx = targetLoc.x - eye.x local dy = targetLoc.y - eye.y local dz = targetLoc.z - eye.z local distSq = dx * dx + dy * dy + dz * dz if distSq <= maxMiningReach * maxMiningReach then targetValid = true end else -- Запасной вариант по центру блока 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 <= maxMiningReach * maxMiningReach then targetValid = true end 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 -- Умный выход из режима ожидания, если новая руда появилась в радиусе maxWaitDistance (3.0 блоков) от сломанного блока if currentTarget and currentTarget.isWaiting and not pickobulusState 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 -- Расчёт дистанции от СЛОМАННОГО БЛОКА до НОВОЙ возродившейся руды local dx = b.pos.x - currentTarget.pos.x local dy = b.pos.y - currentTarget.pos.y local dz = b.pos.z - currentTarget.pos.z local dist = math.sqrt(dx * dx + dy * dy + dz * dz) if dist <= maxWaitDistance then foundBetter = true break end 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 and not pickobulusState then if #scannedBlocks == 0 then local currentRot = getCurrentRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) end if #scannedBlocks > 0 then currentTarget = scannedBlocks[1] currentTargetTicks = 0 currentTarget.history = {} -- Выбираем случайный угол ТОЛЬКО из тех, до которых реально дотянуться local reachableAngles = {} for _, ang in ipairs(currentTarget.angles) do local adx = ang.location.x - eye.x local ady = ang.location.y - eye.y local adz = ang.location.z - eye.z local dSq = adx * adx + ady * ady + adz * adz if dSq <= maxMiningReach * maxMiningReach then table.insert(reachableAngles, ang) end end if #reachableAngles > 0 then local randomIndex = math.random(1, #reachableAngles) currentTarget.targetRot = reachableAngles[randomIndex] else currentTarget.targetRot = currentTarget.angles[1] end table.insert(currentTarget.history, currentTarget.targetRot) if #scannedBlocks > 1 and scannedBlocks[2].angles and #scannedBlocks[2].angles > 0 then -- Аналогично отбираем только доступные углы для предсказания следующей цели local nextReachable = {} for _, ang in ipairs(scannedBlocks[2].angles) do local adx = ang.location.x - eye.x local ady = ang.location.y - eye.y local adz = ang.location.z - eye.z local dSq = adx * adx + ady * ady + adz * adz if dSq <= maxMiningReach * maxMiningReach then table.insert(nextReachable, ang) end end if #nextReachable > 0 then local nextRandomIndex = math.random(1, #nextReachable) currentTarget.nextYaw = nextReachable[nextRandomIndex].yaw currentTarget.nextPitch = nextReachable[nextRandomIndex].pitch else currentTarget.nextYaw = scannedBlocks[2].angles[1].yaw currentTarget.nextPitch = scannedBlocks[2].angles[1].pitch end else currentTarget.nextYaw = nil currentTarget.nextPitch = nil end currentTarget.hasArrived = false currentTarget.nextAimShiftTick = 0 else -- Если нет живых блоков вообще — ищем блок в радиусе ДОСЯГАЕМОСТИ копания игрока (до maxMiningReach), который скоро возродится 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 -- Лимит до maxMiningReach от игрока — чтобы персонаж мог навестись на ожидаемую руду в зоне досягаемости if distSq <= maxMiningReach * maxMiningReach then if ticksLeft < minTicksLeft then minTicksLeft = ticksLeft bestPrediction = data end end end end end else -- Если приоритетная группа не определена, ищем любую сломанную руду в зоне досягаемости игрока for key, data in pairs(brokenBlocks) do if targetIdentifiers[data.identifier] 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 if distSq <= maxMiningReach * maxMiningReach then if ticksLeft < minTicksLeft then minTicksLeft = ticksLeft bestPrediction = data end end end end end end if bestPrediction and bestPrediction.targetRot then local key = bestPrediction.pos.x .. "," .. bestPrediction.pos.y .. "," .. bestPrediction.pos.z local chosenRot = bestPrediction.targetRot local targetAngles = {} if cachedWorldMap and cachedWorldMap[key] and cachedWorldMap[key].angles then targetAngles = cachedWorldMap[key].angles -- Ограничиваем предсказываемую цель только достижимыми углами local predictionReachable = {} for _, ang in ipairs(targetAngles) do local adx = ang.location.x - eye.x local ady = ang.location.y - eye.y local adz = ang.location.z - eye.z local dSq = adx * adx + ady * ady + adz * adz if dSq <= maxMiningReach * maxMiningReach then table.insert(predictionReachable, ang) end end if #predictionReachable > 0 then chosenRot = predictionReachable[math.random(1, #predictionReachable)] end end currentTarget = { pos = bestPrediction.pos, targetRot = chosenRot, identifier = bestPrediction.identifier, isWaiting = true, hasArrived = false, angles = targetAngles, history = { chosenRot }, nextAimShiftTick = 0 } end end end local targetRot = nil local shouldRotate = true -- Логика ротации и наведения if pickobulusState == "aiming" and pickobulusTarget then -- Наведение на блок для Пикоболуса local playerRot = getCurrentRotation() local bestAng = pickobulusTarget.angles[1] -- Применяем случайное смещение local targetYaw = bestAng.yaw + (pickobulusTarget.yawOffset or 0) local targetPitch = bestAng.pitch + (pickobulusTarget.pitchOffset or 0) -- Нормализация углов if targetYaw > 180 then targetYaw = targetYaw - 360 end if targetYaw < -180 then targetYaw = targetYaw + 360 end if targetPitch > 90 then targetPitch = 90 end if targetPitch < -90 then targetPitch = -90 end targetRot = { yaw = targetYaw, pitch = targetPitch } local dyaw = targetYaw - playerRot.yaw if dyaw > 180 then dyaw = dyaw - 360 end if dyaw < -180 then dyaw = dyaw + 360 end local dpitch = targetPitch - playerRot.pitch local diffSq = dyaw * dyaw + dpitch * dpitch -- Увеличиваем порог попадания с учетом смещения if diffSq < 0.64 then pickobulusAimTicks = pickobulusAimTicks + 1 if pickobulusAimTicks >= 4 then -- Даем 4 тика на стабилизацию взгляда player.input.useItem() -- ПКМ (Используем Пикоболус) player.addMessage("§7[§6Hypixel Cry§7] §a💥 Picobulus has been launched!") lastPickobulusTick = globalTicks pickobulusState = nil pickobulusTarget = nil currentTarget = nil -- Сбрасываем цель, чтобы возобновить обычное копание end else pickobulusAimTicks = 0 end elseif currentTarget and currentTarget.targetRot then -- Наведение в обычном режиме local playerRot = getCurrentRotation() 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 end targetRot = currentTarget.targetRot lastActiveRot = { yaw = targetRot.yaw, pitch = targetRot.pitch } elseif lastActiveRot then targetRot = lastActiveRot end if targetRot and shouldRotate then rotations.rotateToYawPitch(targetRot.yaw, targetRot.pitch) end local isAttacking = false if pickobulusState == "aiming" then isAttacking = false -- Отключаем зажатие ЛКМ при броске Пикоболуса elseif currentTarget and shouldRotate and not currentTarget.isWaiting then local ray = player.raycast(maxMiningReach) if ray then if ray.type == "block" and ray.blockPos then local lookedBlock = world.getBlock(ray.blockPos.x, ray.blockPos.y, ray.blockPos.z) local isTargetPos = (ray.blockPos.x == currentTarget.pos.x and ray.blockPos.y == currentTarget.pos.y and ray.blockPos.z == currentTarget.pos.z) if (lookedBlock and targetIdentifiers[lookedBlock.identifier]) or isTargetPos then isAttacking = true end elseif ray.type == "entity" then isAttacking = false end end end local entities = world.getLivingEntities() for _, entity in ipairs(entities) do if entity ~= player.entity and (entity.type == "entity.minecraft.player" or entity.type == "entity.minecraft.villager") 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 end) registerWorldRenderer(function(ctx) if not enabled then return end if player.inventory.isAnyScreenOpened() then return end -- Отображение цели Пикоболуса в виде фиолетового заполненного блока if pickobulusState == "aiming" and pickobulusTarget then local pos = pickobulusTarget.pos if pos then local margin = 0.01 local box = creator.createBox( pos.x - margin, pos.y - margin, pos.z - margin, pos.x + 1 + margin, pos.y + 1 + margin, pos.z + 1 + margin ) ctx.renderFilled(box, 138, 43, 226, 180, true) -- Фиолетовое свечение на всю руду end end if currentTarget and currentTarget.targetRot and currentTarget.targetRot.location and not pickobulusState 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 local margin = 0.002 if not pickobulusState then 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 end) registerKeyEvent(function(key, action) if key == keys.KEY_GRAVE_ACCENT and action == "Press" then enabled = not enabled if enabled then player.addMessage("§7[§6Hypixel Cry§7] §aEnabled") -- Сбрасываем таймер, чтобы Пикоболус сработал мгновенно при включении lastPickobulusTick = globalTicks - pickobulusIntervalTicks lastBrokenBlockPos = nil -- сброс координат при включении -- Принудительно сканируем мир в первый же миг включения local currentRot = getCurrentRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) else player.addMessage("§7[§6Hypixel Cry§7] §cDisabled") local currentRot = getCurrentRotation() player.setRotation(currentRot.yaw, currentRot.pitch) 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 lastBrokenBlockPos = nil -- сброс координат при выключении -- Сброс состояния Пикоболуса и кэша позиций pickobulusState = nil pickobulusTarget = nil pickobulusAimTicks = 0 cachedPickobulusTargets = nil lastPickobulusPlayerPos = { x = 0, y = 0, z = 0 } pickobulusTriggerPending = false pickobulusDelayTicksLeft = -1 end end end) registerMessageEvent(function(text, overlay, json) if not enabled then return end if text and not overlay then local cleanText = stripColorCodes(text) local cleanLower = cleanText:lower() -- Поиск ключевых слов "pickobulus" и "available" в любом регистре if cleanLower:find("pickobulus") and cleanLower:find("available") then -- Сбрасываем кулдаун для мгновенного броска на следующем тике lastPickobulusTick = globalTicks - pickobulusIntervalTicks player.addMessage("§7[§6Hypixel Cry§7] §a⚡ Picobulus is ready (chat trigger)!") end end end) registerUnloadCallback(function() local currentRot = getCurrentRotation() player.setRotation(currentRot.yaw, currentRot.pitch) end)