Загрузка данных


if not game:IsLoaded() then
    game.Loaded:Wait()
end

local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local TeleportService = game:GetService("TeleportService")
local GuiService = game:GetService("GuiService")

local player = Players.LocalPlayer or Players.PlayerAdded:Wait()
task.wait(2.5)

local MAX_PLAYERS = 5
local placeId = game.PlaceId
local currentJobId = game.JobId

local env = (getgenv and getgenv()) or _G

-- Останавливаем любые старые циклы из прошлых запусков скрипта
env.RunId = (env.RunId or 0) + 1
local currentRunId = env.RunId

env.FailedServers = env.FailedServers or {}
env.CandidateServers = env.CandidateServers or {}
env.IsSearching = false
env.IsTeleporting = false

if env.TeleportFailConn then env.TeleportFailConn:Disconnect() end
if env.ErrorMsgConn then env.ErrorMsgConn:Disconnect() end
if env.PlayerAddedConn then env.PlayerAddedConn:Disconnect() end

local cacheFileName = "ServerCache_" .. tostring(placeId) .. ".json"

-- Чтение и запись кэша серверов (чтобы не ловить Rate Limit при перезаходах)
local function loadCache()
    if readfile and isfile and isfile(cacheFileName) then
        local ok, data = pcall(function()
            return HttpService:JSONDecode(readfile(cacheFileName))
        end)
        if ok and type(data) == "table" and data.time and (os.time() - data.time) < 90 and type(data.servers) == "table" then
            return data.servers
        end
    end
    return nil
end

local function saveCache(servers)
    if writefile then
        pcall(function()
            writefile(cacheFileName, HttpService:JSONEncode({
                time = os.time(),
                servers = servers
            }))
        end)
    end
end

local findAliveServers

local function tryNextServer()
    if env.RunId ~= currentRunId then return end

    while #env.CandidateServers > 0 do
        local targetServerId = table.remove(env.CandidateServers, 1)

        if not env.FailedServers[targetServerId] and targetServerId ~= currentJobId then
            env.FailedServers[targetServerId] = true
            env.IsTeleporting = true
            saveCache(env.CandidateServers)

            print("[AutoHop] Подключение к серверу: " .. targetServerId .. " (В пуле осталось: " .. #env.CandidateServers .. ")")

            pcall(function()
                GuiService:ClearError()
            end)

            local success, err = pcall(function()
                TeleportService:TeleportToPlaceInstance(placeId, targetServerId, player)
            end)

            if not success then
                warn("[AutoHop] Ошибка вызова телепорта: " .. tostring(err))
                env.IsTeleporting = false
                task.wait(1.5)
            else
                return
            end
        end
    end

    env.IsTeleporting = false
    env.IsSearching = false
    warn("[AutoHop] Пул серверов закончился. Новый запрос к API через 10 сек...")
    task.wait(10)
    if env.RunId == currentRunId then
        findAliveServers(true)
    end
end

local function handleFailure(reason)
    if env.RunId ~= currentRunId then return end
    if not env.IsTeleporting then return end

    warn("[AutoHop] Сбой входа (" .. tostring(reason) .. "). Пробуем следующий через 2 сек...")

    pcall(function()
        GuiService:ClearError()
    end)

    task.wait(2)

    pcall(function()
        GuiService:ClearError()
    end)

    env.IsTeleporting = false
    tryNextServer()
end

env.TeleportFailConn = TeleportService.TeleportInitFailed:Connect(function(failedPlayer, teleportResult, errorMessage)
    if failedPlayer == player then
        handleFailure(tostring(teleportResult.Name) .. " - " .. tostring(errorMessage))
    end
end)

env.ErrorMsgConn = GuiService.ErrorMessageChanged:Connect(function(errorMessage)
    if errorMessage and errorMessage ~= "" then
        handleFailure("Окно ошибки: " .. tostring(errorMessage))
    end
end)

-- Запрос к API с автоматическим переключением на резервный прокси при Rate Limit
local function fetchServerPage(cursor)
    local endpoints = {
        "https://games.roblox.com/v1/games/" .. placeId .. "/servers/Public?sortOrder=Asc&excludeFullGames=true&limit=100",
        "https://games.roproxy.com/v1/games/" .. placeId .. "/servers/Public?sortOrder=Asc&excludeFullGames=true&limit=100"
    }

    for i, baseUrl in ipairs(endpoints) do
        local url = baseUrl
        if cursor then
            url = url .. "&cursor=" .. HttpService:UrlEncode(cursor)
        end

        local success, decoded = pcall(function()
            local response = game:HttpGet(url)
            return HttpService:JSONDecode(response)
        end)

        if success and type(decoded) == "table" and type(decoded.data) == "table" then
            return decoded
        end

        if i == 1 then
            warn("[AutoHop] Основной API Roblox временно ограничил запросы, пробуем резервное зеркало...")
            task.wait(1)
        end
    end

    return nil
end

findAliveServers = function(ignoreCache)
    if env.RunId ~= currentRunId then return end
    if env.IsSearching or env.IsTeleporting then return end
    env.IsSearching = true

    -- 1. Проверяем сохранённый кэш, чтобы не спамить API
    if not ignoreCache then
        local cached = loadCache()
        if cached and #cached > 0 then
            print("[AutoHop] Загружено серверов из кэша: " .. #cached)
            env.CandidateServers = cached
            tryNextServer()
            return
        end
    end

    print("[AutoHop] Сканирование серверов (ищем от 1 до " .. (MAX_PLAYERS - 1) .. " игроков)...")

    local data = fetchServerPage(nil)
    if not data then
        warn("[AutoHop] Оба API перегружены (Rate Limit). Ждем 15 сек для снятия блокировки...")
        task.wait(15)
        env.IsSearching = false
        if env.RunId == currentRunId then
            findAliveServers(true)
        end
        return
    end

    local collected = {}

    for _, server in ipairs(data.data) do
        if type(server) == "table" and type(server.id) == "string" then
            local playing = tonumber(server.playing) or 0
            local maxPlayers = tonumber(server.maxPlayers) or 0
            local fps = tonumber(server.fps) or 60
            local ping = tonumber(server.ping) or 100

            -- Ищем серверы, где от 1 до 4 игроков, минимум 2 свободных слота и сервер не завис (FPS >= 30)
            if server.id ~= currentJobId and not env.FailedServers[server.id] then
                if playing >= 1 and playing < MAX_PLAYERS and (maxPlayers - playing) >= 2 and fps >= 30 and ping <= 500 then
                    table.insert(collected, { id = server.id, playing = playing })
                end
            end
        end
    end

    -- Сортируем так, чтобы сначала шли серверы с 1 игроком, затем с 2, 3 и 4
    table.sort(collected, function(a, b)
        return a.playing < b.playing
    end)

    env.CandidateServers = {}
    for _, item in ipairs(collected) do
        table.insert(env.CandidateServers, item.id)
    end

    if #env.CandidateServers > 0 then
        print("[AutoHop] Найдено подходящих серверов: " .. #env.CandidateServers)
        saveCache(env.CandidateServers)
        tryNextServer()
    else
        warn("[AutoHop] На первой странице нет серверов меньше " .. MAX_PLAYERS .. " игроков. Повтор через 10 сек...")
        task.wait(10)
        env.IsSearching = false
        if env.RunId == currentRunId then
            findAliveServers(true)
        end
    end
end

local function checkPlayerCount()
    if env.RunId ~= currentRunId then return end
    local currentCount = #Players:GetPlayers()

    if currentCount > MAX_PLAYERS then
        if not env.IsSearching and not env.IsTeleporting then
            warn("[AutoHop] На сервере " .. currentCount .. " игроков (лимит: " .. MAX_PLAYERS .. "). Ищем новый сервер...")
            findAliveServers(false)
        end
    else
        print("[AutoHop] Сервер подходит! Игроков: " .. currentCount .. "/" .. MAX_PLAYERS)
    end
end

checkPlayerCount()

env.PlayerAddedConn = Players.PlayerAdded:Connect(function()
    task.wait(0.5)
    checkPlayerCount()
end)