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


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

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.PlayerAddedConn then env.PlayerAddedConn:Disconnect() end

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

-- Загрузка и сохранение кэша (чтобы сторож 277 знал, куда прыгать прямо с экрана загрузки)
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 type(data.servers) == "table" then
            if type(data.failed) == "table" then
                for k, v in pairs(data.failed) do
                    env.FailedServers[k] = v
                end
            end
            if data.time and (os.time() - data.time) < 120 then
                return data.servers
            end
        end
    end
    return nil
end

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

-- Проверяем, висит ли сейчас окно любой ошибки (277, 772, 267 и т.д.)
local function isErrorOnScreen()
    local okCode, errCode = pcall(function()
        return GuiService:GetErrorCode()
    end)
    if okCode and errCode and errCode ~= Enum.ConnectionError.OK then
        return true
    end

    local okGui, hasPrompt = pcall(function()
        local promptOverlay = CoreGui:FindFirstChild("RobloxPromptGui")
            and CoreGui.RobloxPromptGui:FindFirstChild("promptOverlay")
        if promptOverlay then
            local errPrompt = promptOverlay:FindFirstChild("ErrorPrompt")
            if errPrompt and errPrompt.Visible then
                return true
            end
        end
        return false
    end)
    return okGui and hasPrompt
end

local findAliveServers

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

    if #env.CandidateServers == 0 then
        local cached = loadCache()
        if cached and #cached > 0 then
            env.CandidateServers = cached
        end
    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 lp = Players.LocalPlayer
            local success, err = pcall(function()
                if lp then
                    TeleportService:TeleportToPlaceInstance(placeId, targetServerId, lp)
                else
                    TeleportService:TeleportToPlaceInstance(placeId, targetServerId)
                end
            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

    -- Если мы застряли на экране ошибки 277 и кэш пуст, делаем обычный реконнект в игру, чтобы оживить сеть
    if isErrorOnScreen() then
        warn("[AutoHop] Кэш пуст во время ошибки 277! Перезаходим в плейс...")
        pcall(function() GuiService:ClearError() end)
        pcall(function() TeleportService:Teleport(placeId) end)
        return
    end

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

-- ============================================================================
-- СТОРОЖ ОШИБКИ 277 (Запускается СРАЗУ, даже если игра зависла на загрузке!)
-- ============================================================================
task.spawn(function()
    while env.RunId == currentRunId do
        if isErrorOnScreen() then
            warn("[AutoHop] Обнаружена ошибка 277 / окно дисконнекта! Переподключение через 2 сек...")
            task.wait(2)

            if env.RunId ~= currentRunId then break end

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

            env.IsTeleporting = false
            env.IsSearching = false
            tryNextServer()

            -- Ждем 4 секунды, чтобы телепорт успел начаться; если ошибка не ушла — цикл нажмет еще раз
            task.wait(4)
        end
        task.wait(0.8)
    end
end)

env.TeleportFailConn = TeleportService.TeleportInitFailed:Connect(function(failedPlayer, teleportResult, errorMessage)
    if env.RunId ~= currentRunId then return end
    if not Players.LocalPlayer or failedPlayer == Players.LocalPlayer then
        warn("[AutoHop] Отказ телепорта (" .. tostring(teleportResult.Name) .. "). Меняем сервер...")
        task.wait(2)
        pcall(function() GuiService:ClearError() end)
        env.IsTeleporting = false
        tryNextServer()
    end
end)

-- ============================================================================
-- ОЖИДАНИЕ ЗАГРУЗКИ МИРА (Теперь не блокирует сторожа ошибки 277!)
-- ============================================================================
if not game:IsLoaded() then
    game.Loaded:Wait()
end

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

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
            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

    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] Сканирование серверов (ищем меньше " .. MAX_PLAYERS .. " игроков)...")

    local data = fetchServerPage(nil)
    if not data then
        warn("[AutoHop] Лимит запросов API. Ждем 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

            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

    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] Подходящих серверов на странице нет. Повтор через 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)