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


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 myRunId = env.RunId
env.AttemptId = 0
env.Failed = env.Failed or {}
env.Pool = env.Pool or {}
env.Busy = false

if env.FailConn then env.FailConn:Disconnect() end
if env.JoinConn then env.JoinConn:Disconnect() end

local cacheFile = "HopCache_" .. tostring(placeId) .. ".json"

local function loadCache()
    if isfile and readfile and isfile(cacheFile) then
        local ok, res = pcall(function() return HttpService:JSONDecode(readfile(cacheFile)) end)
        if ok and type(res) == "table" then
            for k, v in pairs(res.failed or {}) do env.Failed[k] = v end
            if res.time and (os.time() - res.time) < 120 and type(res.pool) == "table" then
                return res.pool
            end
        end
    end
    return nil
end

local function saveCache()
    if writefile then
        pcall(function()
            writefile(cacheFile, HttpService:JSONEncode({ time = os.time(), pool = env.Pool, failed = env.Failed }))
        end)
    end
end

local function clearErrorPopup()
    pcall(function()
        GuiService:ClearError()
        local overlay = CoreGui:FindFirstChild("RobloxPromptGui") and CoreGui.RobloxPromptGui:FindFirstChild("promptOverlay")
        if overlay then
            local prompt = overlay:FindFirstChild("ErrorPrompt")
            if prompt and prompt.Visible then
                local btn = prompt:FindFirstChildWhichIsA("TextButton", true) or prompt:FindFirstChildWhichIsA("ImageButton", true)
                if btn and firesignal then firesignal(btn.MouseButton1Click) end
                prompt.Visible = false
            end
        end
        -- Скрываем всплывающие плашки (Unknown Status / Server Unavailable)
        for _, v in ipairs(CoreGui:GetDescendants()) do
            if v:IsA("TextLabel") and v.Visible then
                local txt = string.lower(v.Text or "")
                if string.find(txt, "unknown status") or string.find(txt, "unavailable") or string.find(txt, "ended") then
                    local parentFrame = v:FindFirstAncestorWhichIsA("Frame")
                    if parentFrame then parentFrame.Visible = false end
                end
            end
        end
    end)
end

local function hasError()
    local ok1, code = pcall(function() return GuiService:GetErrorCode() end)
    if ok1 and code and code ~= Enum.ConnectionError.OK then return true end

    local ok2, vis = pcall(function()
        return CoreGui.RobloxPromptGui.promptOverlay.ErrorPrompt.Visible
    end)
    if ok2 and vis then return true end

    local ok3, hasToast = pcall(function()
        for _, v in ipairs(CoreGui:GetDescendants()) do
            if v:IsA("TextLabel") and v.Visible then
                local txt = string.lower(v.Text or "")
                if string.find(txt, "unknown status") or string.find(txt, "unavailable") or string.find(txt, "ended") then
                    return true
                end
            end
        end
        return false
    end)
    return ok3 and hasToast
end

local scanServers

local function hopNext()
    if env.RunId ~= myRunId then return end
    if #env.Pool == 0 then env.Pool = loadCache() or {} end

    while #env.Pool > 0 do
        local id = table.remove(env.Pool, 1)
        if not env.Failed[id] and id ~= currentJobId then
            env.Failed[id] = true
            env.Busy = true
            env.AttemptId = env.AttemptId + 1
            local thisAttempt = env.AttemptId

            saveCache()
            clearErrorPopup()
            print("[AutoHop] Переход на сервер: " .. id .. " (В запасе: " .. #env.Pool .. ")")

            local ok = pcall(function()
                TeleportService:TeleportToPlaceInstance(placeId, id, Players.LocalPlayer)
            end)

            if ok then
                -- Сторожевой таймер против "Unknown Status": если за 6 секунд сервер не пустил, берем следующий
                task.delay(6, function()
                    if env.RunId == myRunId and env.Busy and env.AttemptId == thisAttempt then
                        warn("[AutoHop] Сервер не ответил за 6 сек (Unknown Status / Timeout). Меняем сервер...")
                        clearErrorPopup()
                        env.Busy = false
                        hopNext()
                    end
                end)
                return
            end
            task.wait(1.5)
        end
    end

    env.Busy = false
    if hasError() then
        clearErrorPopup()
        pcall(function() TeleportService:Teleport(placeId) end)
        return
    end
    task.wait(8)
    if env.RunId == myRunId then scanServers(true) end
end

-- Мониторинг ошибок (277, 772, Unknown Status, Unavailable) прямо с экрана загрузки
task.spawn(function()
    while env.RunId == myRunId do
        if hasError() then
            warn("[AutoHop] Перехвачена ошибка входа! Переключаем сервер...")
            clearErrorPopup()
            task.wait(1.5)
            if env.RunId ~= myRunId then break end
            env.AttemptId = env.AttemptId + 1
            env.Busy = false
            hopNext()
            task.wait(4)
        end
        task.wait(0.8)
    end
end)

env.FailConn = TeleportService.TeleportInitFailed:Connect(function(p)
    if env.RunId == myRunId and (not Players.LocalPlayer or p == Players.LocalPlayer) then
        env.AttemptId = env.AttemptId + 1
        clearErrorPopup()
        task.wait(1.5)
        env.Busy = false
        hopNext()
    end
end)

if not game:IsLoaded() then game.Loaded:Wait() end
local lp = Players.LocalPlayer or Players.PlayerAdded:Wait()
task.wait(2.5)
currentJobId = game.JobId

scanServers = function(ignoreCache)
    if env.RunId ~= myRunId or env.Busy then return end
    env.Busy = true

    if not ignoreCache then
        local cached = loadCache()
        if cached and #cached > 0 then
            env.Pool = cached
            hopNext()
            return
        end
    end

    local urls = {
        "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"
    }

    local data = nil
    for _, url in ipairs(urls) do
        local ok, res = pcall(function() return HttpService:JSONDecode(game:HttpGet(url)) end)
        if ok and type(res) == "table" and type(res.data) == "table" then
            data = res
            break
        end
        task.wait(1)
    end

    if not data then
        warn("[AutoHop] Лимит API, ждем 15 сек...")
        task.wait(15)
        env.Busy = false
        if env.RunId == myRunId then scanServers(true) end
        return
    end

    local list = {}
    for _, s in ipairs(data.data) do
        if type(s) == "table" and type(s.id) == "string" then
            local pl = tonumber(s.playing) or 0
            local max = tonumber(s.maxPlayers) or 0
            local fps = tonumber(s.fps) or 60
            local ping = tonumber(s.ping) or 100
            if s.id ~= currentJobId and not env.Failed[s.id] then
                if pl >= 2 and pl < MAX_PLAYERS and (max - pl) >= 5 and fps >= 45 and ping >= 20 and ping <= 350 then
                    table.insert(list, { id = s.id, pl = pl })
                end
            end
        end
    end

    table.sort(list, function(a, b) return a.pl < b.pl end)
    env.Pool = {}
    for _, item in ipairs(list) do table.insert(env.Pool, item.id) end

    if #env.Pool > 0 then
        saveCache()
        hopNext()
    else
        warn("[AutoHop] Подходящих серверов нет, повтор через 10 сек...")
        task.wait(10)
        env.Busy = false
        if env.RunId == myRunId then scanServers(true) end
    end
end

local function checkCount()
    if env.RunId == myRunId and not env.Busy and #Players:GetPlayers() > MAX_PLAYERS then
        warn("[AutoHop] На сервере больше " .. MAX_PLAYERS .. " игроков! Меняем сервер...")
        scanServers(false)
    end
end

checkCount()
env.JoinConn = Players.PlayerAdded:Connect(function()
    task.wait(0.5)
    checkCount()
end)