local starred = {} local cache = {} -- Здесь храним только ID таргетов: cache[id] = true local SEARCH_RADIUS = 0.65 -- Настройки частоты обновлений local SCAN_INTERVAL = 4 -- Сканировать каждые 4 тика (снижает нагрузку) local tickCounter = 0 -- Радиус для повторной проверки закешированных мобов local RECHECK_RADIUS = 10.0 local RECHECK_RADIUS_SQ = RECHECK_RADIUS * RECHECK_RADIUS -- оптимизация: избегаем sqrt -- Локализация для скорости local player = require("player") local world = require("world") local ipairs = ipairs local pairs = pairs local string_find = string.find local math_sqrt = math.sqrt local world_getEntities = world.getLivingEntities local world_getEntitiesInBox = world.getEntitiesInBox local math_floor = math.floor -- Константы для поиска local BOX_EXPAND_Y = 2.0 -- Функция проверки конкретной сущности local function checkIsStarred(entity) if not entity then return false end -- Ищем сущности в коробке над мобом local box = entity.box if not box then return false end -- getEntitiesInBox возвращает список, проверяем его local entitiesAbove = world_getEntitiesInBox(entity, box.expand(0, BOX_EXPAND_Y, 0)) if entity.type == "entity.minecraft.cow" or entity.type == "entity.minecraft.chicken" or entity.type == "entity.minecraft.pig" then local fx = math_floor(entity.x) local fy = math_floor(entity.y) local fz = math_floor(entity.z) if (entity.x == 170.5 and entity.y == 29 and entity.z == -501.5) then return false end if (entity.x == 175.5 and entity.y == 29 and entity.z == -501.5) then return false end if (fx == 185 and fy == 33 and fz == -487) then return false end if (fx == 185 and fy == 30 and fz == -487) then return false end if (fx == 197 and fy == 15 and fz == -488) then return false end if (fx == 197 and fy == 18 and fz == -493) then return false end if (fx == 197 and fy == 17 and fz == -488) then return false end if (entity.x == 197.5 and entity.y == 14 and entity.z == -497.5) then return false end if (entity.x == 197.5 and entity.y == 18.3 and entity.z == -492.5) then return false end if (entity.x == 197.5 and entity.y == 14.986572 and entity.z == -487.5) then return false end end if entitiesAbove then for _, ent in ipairs(entitiesAbove) do -- Проверяем, является ли сущность стойкой для брони if ent and ent.type == "entity.minecraft.armor_stand" then local name = ent.display_name or "" -- Проверяем ключевые слова if string_find(name, "Trackable") or string_find(name, "Untrackable") or string_find(name, "Undetected") or string_find(name, "Endangered") or string_find(name, "Elusive") then return true end end end end return false end -- Функция вычисления квадрата расстояния между игроком и сущностью (только по горизонтали) local function distanceSqToPlayer(entity, playerLoc) if not entity or not entity.position or not playerLoc then return math.huge end local entLoc = entity.position local dx = entLoc.x - playerLoc.x local dz = entLoc.z - playerLoc.z local dy = entLoc.y - playerLoc.y return dx * dx + dz * dz + dy * dy -- игнорируем Y для более естественного поведения end local function updateTargets(playerLoc) local entities = world_getEntities() local mobs = {} local currentTickIDs = {} -- Список ID, которые существуют сейчас for _, entity in ipairs(entities) do -- Проверяем только живых существ, исключая игроков и стойки if entity and entity.type ~= "entity.minecraft.armor_stand" and entity.type ~= "entity.minecraft.player" then local id = entity.id currentTickIDs[id] = true -- Логика обработки кеша: -- 1. Если моб в кеше И находится в радиусе 15 блоков — повторно проверяем -- 2. Если проверка не проходит — удаляем из кеша -- 3. Если моб в кеше, но ДАЛЕКО (>15 блоков) — доверяем кешу (не проверяем) -- 4. Если моба нет в кеше — проверяем обычным способом local inCache = cache[id] local nearby = inCache and (distanceSqToPlayer(entity, playerLoc) <= RECHECK_RADIUS_SQ) if inCache then if nearby then -- Моб рядом — повторно проверяем звезду if checkIsStarred(entity) then mobs[#mobs + 1] = entity else -- Звезда пропала — удаляем из кеша cache[id] = nil end else -- Моб далеко — доверяем кешу без проверки mobs[#mobs + 1] = entity end else -- Моба нет в кеше — проверяем впервые if checkIsStarred(entity) then cache[id] = true mobs[#mobs + 1] = entity end -- Если проверка не прошла — не кешируем, проверим снова через несколько тиков end end end -- Очистка памяти: удаляем из кеша мобов, которых больше нет в мире for id in pairs(cache) do if not currentTickIDs[id] then cache[id] = nil end end return mobs end registerClientTick(function() -- Получаем локацию игрока один раз за тик local playerLoc = player.getPos() local loc = player.getLocation() if loc and (loc == "THE_FARMING_ISLAND") then tickCounter = tickCounter + 1 -- Запускаем поиск только раз в SCAN_INTERVAL тиков if tickCounter >= SCAN_INTERVAL then starred = updateTargets(playerLoc) tickCounter = 0 end else if #starred > 0 then starred = {} end if next(cache) ~= nil then cache = {} end end end) registerWorldRenderer(function(context) -- Рендерим список starred for i = 1, #starred do local entity = starred[i] -- Проверка на nil на случай рассинхрона if entity and entity.box then context.renderOutline(entity.box, 255, 52, 255, 140, 2, true) context.renderLineFromCursor(entity.x, entity.y + entity.height, entity.z, 255, 52, 255, 140, 2) end end end)