local player = require("player") local world = require("world") local creator = require("creator") local keys = require("keys") local rotations = require("silent_rotations_v2") rotations.setRotationSpeed(11) local enabled = false local step = 1.37 local pickobulusStep = 10 local minShiftDelay = 10 local maxShiftDelay = 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 respawnTextScale = 1.0 -- Размер текста таймера local respawnTextThroughWalls = true -- Видимость сквозь стены (true/false) local respawnTextRed = 255 -- Компонент красного цвета (0-255) local respawnTextGreen = 215 -- Компонент зеленого цвета (0-255) local respawnTextBlue = 0 -- Компонент синего цвета (0-255) -- Базовый список всех валидных блоков 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, } -- НАСТРОЙКИ ИНДИВИДУАЛЬНОГО ВРЕМЕНИ РЕСПАВНА (20 тиков = 1 секунда) local blockRespawnDelays = { ["minecraft:coal_block"] = 200, -- Уголь: 15 секунд (300 тиков) ["minecraft:iron_block"] = 200, -- Железо: 15 секунд ["minecraft:gold_block"] = 200, -- Золото: 20 секунд (400 тиков) ["minecraft:diamond_block"] = 200, -- Алмаз: 30 секунд (600 тиков) ["minecraft:lapis_block"] = 200, -- Лазурит: 15 секунд ["minecraft:emerald_block"] = 200, -- Изумруд: 30 секунд ["minecraft:cyan_terracotta"] = 300, ["minecraft:light_blue_wool"] = 300, ["minecraft:polished_diorite"] = 300, ["minecraft:dark_prismarine"] = 300, ["minecraft:gray_wool"] = 300, ["minecraft:prismarine"] = 300, ["minecraft:prismarine_bricks"] = 300, -- Для остальных неуказанных блоков будет применяться defaultRespawnDelayTicks } -- Группировка: связываем разные блоки в один виртуальный тип руды 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 -- Функция получения задержки респавна для конкретного блока local function getBlockRespawnDelay(identifier) if identifier and blockRespawnDelays[identifier] then return blockRespawnDelays[identifier] end return defaultRespawnDelayTicks -- Значение по умолчанию, если блока нет в таблице 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) local function round(num, numDecimalPlaces) local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end registerServerSideTeleportEvent(function(x, y, z) if x and y and z then enabled = false player.addMessage("§7[§6Hypixel Cry§7] §cYou teleported!") local pos = player.getPos() world.playSound(pos.x, pos.y, pos.z, "minecraft:block.anvil.place", 100, 1) end end) registerServerSideRotationEvent(function(yaw, pitch) if yaw < -180 or yaw > 180 then return end if pitch < -90 or pitch > 90 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) player.setSilentRotation(yaw, pitch, true, true, 1000) 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 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("§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 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 -- Вычисляем расстояние до ОПОРНОЙ ТОЧКИ (последнего вскопанного блока или глаз игрока) 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 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 = player.getSilentRotation() 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 blockIdentifier = cachedWorldMap[key].identifier local delay = getBlockRespawnDelay(blockIdentifier) brokenBlocks[key] = { respawnAtServerTick = currentTick + delay, 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 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 = player.getSilentRotation() 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.getSilentRotation() 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 -- Если нет живых блоков вообще — ищем блок в радиусе ДОСЯГАЕМОСТИ копания игрока (до 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 chosenRot = targetAngles[math.random(1, #targetAngles)] 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 = player.getSilentRotation() 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 = player.getSilentRotation() 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(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 -- Отображение цели Пикоболуса в виде фиолетового заполненного блока 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 -- Отображение таймера времени респавна local currentTick = (serverGameTime and serverGameTime > 0) and serverGameTime or globalTicks for key, data in pairs(brokenBlocks) do local ticksLeft = data.respawnAtServerTick - currentTick if ticksLeft > 0 then local secondsLeft = ticksLeft / 20 -- Переводим тики в секунды (20 тиков = 1 сек) local text = string.format("%.1fs", secondsLeft) -- Центрируем текст по середине сломанного блока local rx = data.pos.x + 0.5 local ry = data.pos.y + 0.5 local rz = data.pos.z + 0.5 -- Безопасный вызов (проверяем, доступна ли функция в ctx или глобально) if ctx and ctx.renderText then ctx.renderText(rx, ry, rz, text, respawnTextScale, respawnTextRed, respawnTextGreen, respawnTextBlue, respawnTextThroughWalls) else renderText(rx, ry, rz, text, respawnTextScale, respawnTextRed, respawnTextGreen, respawnTextBlue, respawnTextThroughWalls) 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.getSilentRotation() scannedBlocks = updateWorldAndDominance(currentRot.yaw, currentRot.pitch) else player.addMessage("§7[§6Hypixel Cry§7] §cDisabled") local rot = player.getSilentRotation() player.setRotation(rot.yaw, rot.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 rot = player.getSilentRotation() player.setRotation(rot.yaw, rot.pitch) end)