local player = require("player") local world = require("world") local creator = require("creator") local silentRotations = require("silent_rotations_v2") silentRotations.setRotationSpeed(13) local RAY_IDS = { 'minecraft:chest', } local ORE_BLOCKS = { ["minecraft:chest"] = true, } -- Таблицы истории и черных списков local clickedHistory = {} local permanentlyLooted = {} local pendingChests = {} -- Переменные для задержки (3-4 тика) перед поворотом local currentTargetKey = nil local targetDetectionTicks = 0 local ticksRequiredToTurn = 3 -- СОСТОЯНИЯ КАМЕРЫ: -- 0 = Свободный полет -- 1 = Захват сундука -- 2 = Возврат домой local cameraState = 0 -- Переменные для рендера подсветок local blockTargeting = {} local blockClicked = {} local lastClickedKey = nil -- Переменные для хранения текущего случайного смещения local offsetX, offsetY, offsetZ = 0, 0, 0 -- Функция генерации случайной задержки в 3-4 тика local function getRandomTargetTicks() return math.random(2, 5) end -- Функция генерации случайного смещения от 0.1 до 0.3 local function getRandomOffset() local val = 0.1 + math.random() * 0.2 if math.random() < 0.5 then return -val else return val end end -- Умная очистка истории local function cleanFarHistory(playerPos) for posKey, _ in pairs(clickedHistory) do if not pendingChests[posKey] then local x, y, z = posKey:match("(%-?%d+),(%-?%d+),(%-?%d+)") if x and y and z then local dx = tonumber(x) + 0.5 - playerPos.x local dy = tonumber(y) + 0.5 - playerPos.y local dz = tonumber(z) + 0.5 - playerPos.z if math.sqrt(dx * dx + dy * dy + dz * dz) > 15 then clickedHistory[posKey] = nil end end end end for posKey, _ in pairs(permanentlyLooted) do local x, y, z = posKey:match("(%-?%d+),(%-?%d+),(%-?%d+)") if x and y and z then local dx = tonumber(x) + 0.5 - playerPos.x local dy = tonumber(y) + 0.5 - playerPos.y local dz = tonumber(z) + 0.5 - playerPos.z if math.sqrt(dx * dx + dy * dy + dz * dz) > 15 then permanentlyLooted[posKey] = nil end end end end local function getBlocksInArea(playerPos, hDist, vDist) local blocks = {} local startX, endX = math.floor(playerPos.x - hDist), math.floor(playerPos.x + hDist) local startY, endY = math.floor(playerPos.y - vDist), math.floor(playerPos.y + vDist) local startZ, endZ = math.floor(playerPos.z - hDist), math.floor(playerPos.z + hDist) for x = startX, endX do for y = startY, endY do for z = startZ, endZ do local posKey = x .. "," .. y .. "," .. z if not clickedHistory[posKey] and not permanentlyLooted[posKey] then local block = world.getBlock(x, y, z) if block and ORE_BLOCKS[block.identifier] then local dist = math.sqrt( (x + 0.5 - playerPos.x) ^ 2 + (y + 0.5 - playerPos.y) ^ 2 + (z + 0.5 - playerPos.z) ^ 2 ) table.insert(blocks, { pos = { x = x, y = y, z = z }, dist = dist, key = posKey }) end end end end end return blocks end registerClientTick(function() local pos = player.getPos() cleanFarHistory(pos) local location = player.getLocation() if location ~= 'CRYSTAL_HOLLOWS' then clickedHistory = {} permanentlyLooted = {} pendingChests = {} cameraState = 0 blockTargeting = {} blockClicked = {} lastClickedKey = nil currentTargetKey = nil targetDetectionTicks = 0 silentRotations.stop() return end -- Обработка таймаута звука (5 тиков) for key, data in pairs(pendingChests) do data.ticks = data.ticks + 1 if data.ticks > 5 then clickedHistory[key] = nil pendingChests[key] = nil end end local blocks = getBlocksInArea(pos, 7, 7) local activeBlock = nil -- Ищем самый близкий валидный сундук в радиусе клика (5 блоков) for _, block in ipairs(blocks) do if block.dist <= 5 then activeBlock = block break end end local foundChest = false if activeBlock then -- Логика задержки: проверяем, тот ли это сундук, что и на прошлом тике if currentTargetKey == activeBlock.key then targetDetectionTicks = targetDetectionTicks + 1 else -- Если сундук новый или сменился, сбрасываем счетчик и генерируем случайный порог (3-4 тика) currentTargetKey = activeBlock.key targetDetectionTicks = 1 ticksRequiredToTurn = getRandomTargetTicks() end -- Начинаем поворот только если сундук успешно "удерживается" в зоне видимости нужное время if targetDetectionTicks >= ticksRequiredToTurn then foundChest = true blockTargeting = activeBlock.pos if cameraState ~= 1 or blockClicked.x ~= activeBlock.pos.x or blockClicked.y ~= activeBlock.pos.y or blockClicked.z ~= activeBlock.pos.z then offsetX = getRandomOffset() offsetY = getRandomOffset() offsetZ = getRandomOffset() end cameraState = 1 local blockRot = world.getRotation( activeBlock.pos.x + 0.5 + offsetX, activeBlock.pos.y + 0.5 + offsetY, activeBlock.pos.z + 0.5 + offsetZ ) silentRotations.rotateToYawPitch(blockRot.yaw, blockRot.pitch) silentRotations.update() local rayResult = player.raycastToBlocksFromIdentifier(4.5, RAY_IDS) if rayResult and rayResult.type == "block" then clickedHistory[activeBlock.key] = true blockClicked = rayResult.blockpos lastClickedKey = activeBlock.key pendingChests[activeBlock.key] = { ticks = 0, pos = activeBlock.pos } player.input.interactBlock(rayResult) blockTargeting = {} -- Сбрасываем цель, так как мы по ней уже успешно кликнули currentTargetKey = nil targetDetectionTicks = 0 end end else -- Если сундуков нет, сбрасываем прогресс ожидания currentTargetKey = nil targetDetectionTicks = 0 end -- ЕСЛИ СУНДУКОВ БОЛЬШЕ НЕТ (ИЛИ ОНИ ЕЩЕ НЕПРОГРЕТЫ ЗАДЕРЖКОЙ) if not foundChest then blockTargeting = {} if cameraState == 1 then cameraState = 2 end if cameraState == 2 then local currentRealRot = player.getRotation() local targetYaw = currentRealRot.yaw local targetPitch = currentRealRot.pitch silentRotations.rotateToYawPitch(targetYaw, targetPitch) silentRotations.update() local sRot = player.getSilentRotation() local currentSilentYaw = sRot.yaw or sRot local currentSilentPitch = sRot.pitch or 0 local yawDiff = (targetYaw - currentSilentYaw + 180) % 360 - 180 local pitchDiff = targetPitch - currentSilentPitch if math.abs(yawDiff) < 1.5 and math.abs(pitchDiff) < 1.5 then cameraState = 0 silentRotations.stop() end end end end) -- РЕГИСТРАЦИЯ ЗВУКА registerSoundPlay(function(info) local name = info.name if name == "minecraft:block.chest.open" or name == "block.chest.open" then local sX = info.position.x local sY = info.position.y local sZ = info.position.z for key, data in pairs(pendingChests) do if sX then local bPos = data.pos local soundDist = math.sqrt((bPos.x + 0.5 - sX) ^ 2 + (bPos.y + 0.5 - sY) ^ 2 + (bPos.z + 0.5 - sZ) ^ 2) if soundDist <= 4.0 then player.addMessage("Sound detected") pendingChests[key] = nil end else pendingChests[key] = nil break end end end end) -- ДЕТЕКТ СООБЩЕНИЯ ИЗ ЧАТА registerMessageEvent(function(text, overlay, json) if text:find("This chest has already been looted") then local targetKey = nil for key, _ in pairs(pendingChests) do targetKey = key break end if not targetKey then targetKey = lastClickedKey end if targetKey then clickedHistory[targetKey] = nil pendingChests[targetKey] = nil permanentlyLooted[targetKey] = true end end end) registerWorldRenderer(function(ctx) if blockTargeting.x then ctx.renderFilled( creator.createBox(blockTargeting.x, blockTargeting.y, blockTargeting.z, blockTargeting.x + 1.0, blockTargeting.y + 1.0, blockTargeting.z + 1.0), 0, 255, 0, 1, true) end if blockClicked.x then ctx.renderFilled( creator.createBox(blockClicked.x, blockClicked.y, blockClicked.z, blockClicked.x + 1.0, blockClicked.y + 1.0, blockClicked.z + 1.0), 255, 0, 0, 1, true) end end)