local player = require("player") local world = require("world") local creator = require("creator") local modules = require("modules") local keys = require("keys") local silentRotations = require("silent_rotations_v2") silentRotations.setRotationSpeed(13) local ids = { ['minecraft:chest'] = true } local ORE_BLOCKS = { ["minecraft:chest"] = true, } local opened = 0 -- Таблицы истории и черных списков 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 lastRaycastLocation = 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") enabled = false 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 lastRaycastLocation = nil silentRotations.stop() return end -- Обработка таймаута звука (5 тиков) for key, data in pairs(pendingChests) do data.ticks = data.ticks + 1 if data.ticks > 12 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 visiblePoints = {} -- Таблица для хранения ВСЕХ успешных рейкастов для этого блока -- Сканируем блок по сетке 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 -- Вместо break собираем данные рейкаста в список local pX, pY, pZ local rLoc = nil if rayCheck.location then rLoc = rayCheck.location pX = rayCheck.location.x - (block.pos.x + 0.5) pY = rayCheck.location.y - (block.pos.y + 0.5) pZ = rayCheck.location.z - (block.pos.z + 0.5) else pX = (dx / 10) - 0.5 pY = (dy / 10) - 0.5 pZ = (dz / 10) - 0.5 rLoc = { x = block.pos.x + (dx / 10), y = block.pos.y + (dy / 10), z = block.pos.z + (dz / 10) } end table.insert(visiblePoints, { ox = pX, oy = pY, oz = pZ, loc = rLoc }) end end end end end -- Если у блока нашлись видимые точки, выбираем ОДНУ случайную из них if #visiblePoints > 0 then local randomPoint = visiblePoints[math.random(1, #visiblePoints)] activeBlock = block foundOffsetX = randomPoint.ox foundOffsetY = randomPoint.oy foundOffsetZ = randomPoint.oz lastRaycastLocation = randomPoint.loc 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 = {} opened = opened + 1 player.addMessage("Chests opened: " .. opened) 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 pos = player.getPos() if not pos then return end local hDist, vDist = 7, 7 local startX, endX = math.floor(pos.x - hDist), math.floor(pos.x + hDist) local startY, endY = math.floor(pos.y - vDist), math.floor(pos.y + vDist) local startZ, endZ = math.floor(pos.z - hDist), math.floor(pos.z + hDist) for x = startX, endX do for y = startY, endY do for z = startZ, endZ do local block = world.getBlock(x, y, z) if block and block.identifier == "minecraft:chest" then local posKey = x .. "," .. y .. "," .. z clickedHistory[posKey] = nil permanentlyLooted[posKey] = nil pendingChests[posKey] = nil end end end end blockTargeting = {} currentTargetKey = nil targetDetectionTicks = 0 end end) registerKeyEvent(function(key, action) if key == keys.KEY_GRAVE_ACCENT and action == "Press" then enabled = not enabled if enabled then player.addMessage("Enabled") else player.addMessage("Disabled") 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, 140, true) end -- Рендер маленькой красной точки на месте рейкаста if lastRaycastLocation then local size = 0.05 -- Радиус точки (итоговый размер кубика 0.03x0.03x0.03) ctx.renderFilled( creator.createBox( lastRaycastLocation.x - size, lastRaycastLocation.y - size, lastRaycastLocation.z - size, lastRaycastLocation.x + size, lastRaycastLocation.y + size, lastRaycastLocation.z + size ), 255, 0, 0, 140, true ) end end)