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 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 foundOffsetX, foundOffsetY, foundOffsetZ = 0.5, 0.5, 0.5 local headPos = player.getEyePosition() -- 1. Поиск сундука с многоточечным рейкастом от начала до конца блока for _, block in ipairs(blocks) do if block.dist <= 5 then local isVisible = false -- Сканируем блок по сетке, чтобы найти любую видимую точку for dx = 1, 9, 2 do for dy = 1, 9, 2 do for dz = 1, 9, 2 do local targetX = block.pos.x + (dx / 10) local targetY = block.pos.y + (dy / 10) local targetZ = block.pos.z + (dz / 10) local checkRot = world.getRotation(targetX, targetY, targetZ) local rayCheck = world.raycastFromRotation({ startX = headPos.x, startY = headPos.y, startZ = headPos.z, yaw = checkRot.yaw, pitch = checkRot.pitch, range = 5.5, include_fluid = false, include_entity = true }) 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 isVisible = true -- Если у рейкаста есть точная локация попадания, вычисляем смещение относительно центра if rayCheck.location then foundOffsetX = rayCheck.location.x - (block.pos.x + 0.5) foundOffsetY = rayCheck.location.y - (block.pos.y + 0.5) foundOffsetZ = rayCheck.location.z - (block.pos.z + 0.5) else -- Запасной вариант, если location пустой foundOffsetX = (dx / 10) - 0.5 foundOffsetY = (dy / 10) - 0.5 foundOffsetZ = (dz / 10) - 0.5 end break end end end if isVisible then break end end if isVisible then break end end if isVisible then break 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 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 = foundOffsetX offsetY = foundOffsetY offsetZ = foundOffsetZ 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 headRot = player.getSilentRotation() local rayResult = world.raycastFromRotation({ startX = headPos.x, startY = headPos.y, startZ = headPos.z, yaw = headRot.yaw, pitch = headRot.pitch, range = 4.5, include_fluid = false, include_entity = true }) if rayResult and rayResult.type == "block" and rayResult.blockPos then local block = world.getBlock(rayResult.blockPos) if block and block.identifier == "minecraft:chest" 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 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 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)