local player = require("player") local world = require("world") local creator = require("creator") local keys = require("keys") local rotations = require("rotations_v3") rotations.setRotationSpeed(13) rotations.setModifier(3) local enabled = false local step = 1.37 local pickobulusStep = 10 local minShiftDelay = 10 local maxShiftDelay = 15 local debug = false local edgeMargin = 0.15 local useCombinedScore = true -- НАСТРОЙКИ РЕСПАВНА И ОЖИДАНИЯ 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", ["minecraft:coal_block"] = "ore", ["minecraft:iron_block"] = "ore", ["minecraft:redstone_block"] = "ore", ["minecraft:gold_block"] = "ore", ["minecraft:diamond_block"] = "ore", ["minecraft:lapis_block"] = "ore", ["minecraft:emerald_block"] = "ore" } -- Список приоритетов внутри mithril (чем меньше число, тем выше приоритет) local blockConfig = { ["minecraft:polished_diorite"] = { group = "mithril", priority = 1 }, ["minecraft:light_blue_wool"] = { group = "mithril", priority = 2 }, ["minecraft:prismarine"] = { group = "mithril", priority = 3 }, ["minecraft:dark_prismarine"] = { group = "mithril", priority = 4 }, ["minecraft:gray_wool"] = { group = "mithril", priority = 5 }, ["minecraft:cyan_terracotta"] = { group = "mithril", priority = 6 }, ["minecraft:prismarine_bricks"]= { group = "mithril", priority = 7 }, ["minecraft:coal_block"] = { group = "ore", priority = 7 }, ["minecraft:iron_block"] = { group = "ore", priority = 7 }, ["minecraft:redstone_block"] = { group = "ore", priority = 7 }, ["minecraft:gold_block"] = { group = "ore", priority = 7 }, ["minecraft:diamond_block"] = { group = "ore", priority = 7 }, ["minecraft:lapis_block"] = { group = "ore", priority = 7 }, ["minecraft:emerald_block"]= { group = "ore", priority = 7 }, } -- Вспомогательная функция для удаления цветовых кодов майнкрафта (§a, §7 и т.д.) local function stripColorCodes(str) if not str then return "" end return str:gsub("§.", "") end -- Вспомогательная функция получения данных блока local function getBlockData(identifier) return blockConfig[identifier] 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 = player.getRotation() 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 = 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 faceDist1, faceDist2 if dist_x <= dist_y and dist_x <= dist_z then faceDist1, faceDist2 = dist_y, dist_z elseif dist_y <= dist_x and dist_y <= dist_z then faceDist1, faceDist2 = dist_x, dist_z else faceDist1, faceDist2 = dist_x, dist_y end if math.min(faceDist1, faceDist2) >= edgeMargin then if not cachedWorldMap[key] then cachedWorldMap[key] = { pos = { x = bx, y = by, z = bz }, identifier = block.identifier, angles = {}, angleDiff = angleDiff, location = loc } end local centerScore = math.min(faceDist1, faceDist2) local angleData = { yaw = yaw, pitch = pitch, location = loc, centerScore = centerScore } table.insert(cachedWorldMap[key].angles, angleData) if angleDiff < cachedWorldMap[key].angleDiff then cachedWorldMap[key].angleDiff = angleDiff end 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 config = nil if currentBlock and blockConfig[currentBlock.identifier] then config = blockConfig[currentBlock.identifier] b.identifier = currentBlock.identifier b.priority = config.priority -- Сохраняем приоритет в объект блока aliveTargetsMap[key] = b if brokenBlocks[key] then brokenBlocks[key] = nil end elseif brokenBlocks[key] then config = blockConfig[brokenBlocks[key].identifier] end if config then local group = config.group 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 player.addMessage("§7[§6Hypixel Cry§7] §a🎯 Group priority: " .. dominantGroup .. " (" .. 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 local cfg = blockConfig[b.identifier] if cfg and cfg.group == targetGroup then -- Вычисляем дистанцию и углы 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 local edx = (b.pos.x + 0.5) - headPos.x local edy = (b.pos.y + 0.5) - headPos.y local edz = (b.pos.z + 0.5) - headPos.z b.eyeDistSq = edx * edx + edy * edy + edz * edz -- Минимальный угол отклонения 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) if a.priority ~= b.priority then return a.priority < b.priority end local scoreA = useCombinedScore and a.angleDiff * a.eyeDistSq or a.angleDiff local scoreB = useCombinedScore and b.angleDiff * b.eyeDistSq or b.angleDiff return scoreA < scoreB 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 = player.getRotation() 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 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 } -- Если текущий разрушенный блок совпадает с нашей активной целью, запускаем задержку 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 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 = player.getRotation() 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 > 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 targetValid = (currentTarget ~= nil) 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 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 -- Умный выход из режима ожидания, если новая руда появилась в радиусе 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 = player.getRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) end if #scannedBlocks > 0 then currentTarget = scannedBlocks[1] currentTargetTicks = 0 currentTarget.history = {} local totalWeight = 0 for _, ang in ipairs(currentTarget.angles) do totalWeight = totalWeight + ang.centerScore end local r = math.random() * totalWeight local cumulative = 0 local chosenIdx = 1 for i, ang in ipairs(currentTarget.angles) do cumulative = cumulative + ang.centerScore if r <= cumulative then chosenIdx = i break end end currentTarget.targetRot = currentTarget.angles[chosenIdx] table.insert(currentTarget.history, currentTarget.targetRot) if #scannedBlocks > 1 and #scannedBlocks[2].angles > 0 then local nextTotalWeight = 0 for _, ang in ipairs(scannedBlocks[2].angles) do nextTotalWeight = nextTotalWeight + ang.centerScore end local nextR = math.random() * nextTotalWeight local nextCumulative = 0 local nextChosenIdx = 1 for i, ang in ipairs(scannedBlocks[2].angles) do nextCumulative = nextCumulative + ang.centerScore if nextR <= nextCumulative then nextChosenIdx = i break end end currentTarget.nextYaw = scannedBlocks[2].angles[nextChosenIdx].yaw currentTarget.nextPitch = scannedBlocks[2].angles[nextChosenIdx].pitch else currentTarget.nextYaw = nil currentTarget.nextPitch = nil end currentTarget.hasArrived = false currentTarget.nextAimShiftTick = 0 else -- Если нет живых блоков вообще — ищем блок в радиусе ДОСЯГАЕМОСТИ копания игрока (до 5 блоков), который скоро возродится 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 -- Лимит 5.0 блоков от игрока — чтобы персонаж мог навестись на ожидаемую руду в зоне досягаемости if distSq <= 5.0 * 5.0 then if ticksLeft < minTicksLeft then minTicksLeft = ticksLeft bestPrediction = data end end end end end else -- Если приоритетная группа не определена, ищем любую сломанную руду в зоне досягаемости игрока (до 5.0 блоков) 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 <= 5.0 * 5.0 then 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 local targetAngles = {} if cachedWorldMap and cachedWorldMap[key] and cachedWorldMap[key].angles then targetAngles = cachedWorldMap[key].angles if #targetAngles > 0 then local predTotalWeight = 0 for _, ang in ipairs(targetAngles) do predTotalWeight = predTotalWeight + ang.centerScore end local predR = math.random() * predTotalWeight local predCumulative = 0 local predChosenIdx = 1 for i, ang in ipairs(targetAngles) do predCumulative = predCumulative + ang.centerScore if predR <= predCumulative then predChosenIdx = i break end end chosenRot = targetAngles[predChosenIdx] 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 ok, bestAng = pcall(function() return pickobulusTarget.angles and pickobulusTarget.angles[1] end) if not ok or not bestAng then pickobulusState = nil pickobulusTarget = nil return end local yaw, pitch ok, yaw = pcall(function() return tonumber(bestAng.yaw) end) if not ok then yaw = nil end ok, pitch = pcall(function() return tonumber(bestAng.pitch) end) if not ok then pitch = nil end if not yaw or not pitch then pickobulusState = nil pickobulusTarget = nil return end -- Наведение на блок для Пикоболуса local playerRot = player.getRotation() -- Применяем случайное смещение local ok2, targetYaw = pcall(function() return yaw + (pickobulusTarget.yawOffset or 0) end) if not ok2 then targetYaw = nil end local ok3, targetPitch = pcall(function() return pitch + (pickobulusTarget.pitchOffset or 0) end) if not ok3 then targetPitch = nil end if not targetYaw or not targetPitch then pickobulusState = nil pickobulusTarget = nil return end -- Нормализация углов 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 = 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 end targetRot = currentTarget.targetRot lastActiveRot = { yaw = targetRot.yaw, pitch = targetRot.pitch } elseif lastActiveRot then targetRot = lastActiveRot end if targetRot and shouldRotate then local playerRot = player.getRotation() local rotYaw = targetRot.yaw local dyaw = rotYaw - playerRot.yaw if dyaw > 180 then rotYaw = rotYaw - 360 elseif dyaw < -180 then rotYaw = rotYaw + 360 end rotations.rotateToYawPitch(rotYaw, 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(5.0) 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 rotations.update() -- Отображение цели Пикоболуса в виде фиолетового заполненного блока 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 if debug then local dotSize = 0.01 if cachedWorldMap then for _, b in pairs(cachedWorldMap) do for _, ang in ipairs(b.angles) do local loc = ang.location local box = creator.createBox( loc.x - dotSize, loc.y - dotSize, loc.z - dotSize, loc.x + dotSize, loc.y + dotSize, loc.z + dotSize ) ctx.renderFilled(box, 255, 255, 0, 200, false) end end end dotSize = 0.05 if cachedPickobulusTargets then for _, b in ipairs(cachedPickobulusTargets) do local aok, loc = pcall(function() return b.angles and b.angles[1] and b.angles[1].location end) if aok and loc then local box = creator.createBox( loc.x - dotSize, loc.y - dotSize, loc.z - dotSize, loc.x + dotSize, loc.y + dotSize, loc.z + dotSize ) ctx.renderFilled(box, 0, 255, 255, 200, false) end end 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 = player.getRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) else player.addMessage("§7[§6Hypixel Cry§7] §cDisabled") 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 if key == keys.KEY_HOME and action == "Press" then debug = not debug player.addMessage("§7[§6Hypixel Cry§7] §7Debug: " .. (debug and "§aON" or "§cOFF")) 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)