local player = require("player") local world = require("world") local creator = require("creator") local modules = require("modules") local silentRotations = require("silent_rotations_v2") silentRotations.setRotationSpeed(13) local ids = { ['minecraft:chest'] = true } local ORE_BLOCKS = { ["minecraft:chest"] = true, } -- Таблицы истории и черных списков local enabled = 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(3, 4) 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 registerServerSideRotationEvent(function(yaw, pitch) if yaw and pitch then player.addMessage("You got rotated") modules.unloadScript(currentScriptName) 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() if not enabled then return end 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 > 7 then clickedHistory[key] = nil pendingChests[key] = nil end end local blocks = getBlocksInArea(pos, 7, 7) local activeBlock = nil local headPos = player.getEyePosition() -- 1. Поиск сундука с жесткой проверкой на препятствия for _, block in ipairs(blocks) do if block.dist <= 5 then local checkRot = world.getRotation(block.pos.x + 0.5, block.pos.y + 0.5, block.pos.z + 0.5) -- Делаем рейкаст без каких-либо фильтров (чтобы он сталкивался ВООБЩЕ СО ВСЕМ) local rayCheck = world.raycastFromRotation({ startX = headPos.x, startY = headPos.y, startZ = headPos.z, yaw = checkRot.yaw, pitch = checkRot.pitch, range = 5.2, -- чуть с запасом, учитывая расстояние до центра блока include_fluid = false, include_entity = false }) -- ИЗМЕНЕНИЕ: Тщательная и строгая проверка. -- Луч должен попасть во что-то, это должен быть блок, и его координаты ДОЛЖНЫ идеально совпасть с сундуком. -- Если между нами камень, то рейкаст вернет координаты камня, и проверка bPos не пройдет! if rayCheck and rayCheck.type == "block" and rayCheck.blockpos then if rayCheck.blockpos.x == block.pos.x and rayCheck.blockpos.y == block.pos.y and rayCheck.blockpos.z == block.pos.z then activeBlock = block break end end end end local foundChest = false -- 2. Если нашли ВИДИМЫЙ сундук, запускаем логику задержки тиков и последующего ротейта if activeBlock then if currentTargetKey == activeBlock.key then targetDetectionTicks = targetDetectionTicks + 1 else currentTargetKey = activeBlock.key targetDetectionTicks = 1 ticksRequiredToTurn = getRandomTargetTicks() end -- Если сундук успешно прошёл валидацию видимостью в течение 3-4 тиков 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, { 'minecraft:chest' }) 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.x or (info.position and info.position.x) or (info.pos and info.pos.x) local sY = info.y or (info.position and info.position.y) or (info.pos and info.pos.y) local sZ = info.z or (info.position and info.position.z) or (info.pos and info.pos.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 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)