-- ============================================================================ -- gag2_petscan.lua — SCAN pet hoang ĐANG BÁN trong Grow a Garden 2 -- Main (Garden Valley) PlaceId 97598239454123 -- FallHarvest PlaceId 126987765280963 ← auto qua bằng CONFIG.World -- -- ── NGUỒN DỮ LIỆU (khảo sát live 2026-08-02) ──────────────────────────────── -- workspace.Map.WildPetRef.WildPet_ [Part, Transparency=1, Anchored] -- PetName = "Bunny" loài -- Rarity = "Common" độ hiếm -- Price = 20000 giá mua -- Lifetime = 420 sống được bao lâu (giây) -- SpawnedAt = 1785727031.58 mốc server-time lúc spawn -- OwnerUserId = 0 0 = CHƯA AI MUA -- OwnerName = "" -- State = "wandering_walking" -- -- ⚠ ĐỪNG dùng workspace.Map.WildPetSpawns — đó chỉ là LỚP HIỂN THỊ. -- SpawnPetController (PlayerScripts.Controllers) L2890 bind WildPetRef, rồi L1597 mới -- clone model sang WildPetSpawns. Nên WildPetRef tới TRƯỚC, và chứa đủ Rarity/Price/ -- Lifetime/Owner mà folder hiển thị hoàn toàn không có. -- -- ── CƠ CHẾ MUA (chỉ ghi lại, script này KHÔNG mua) ────────────────────────── -- SpawnPetController L1714-1759 dựng ProximityPrompt "BuyPrompt": -- ActionText="Buy" ObjectText="¢" HoldDuration=1 MaxActivationDistance=12 -- RequiresLineOfSight=false -- L1995: Triggered → Networking.Pets.WildPetTame:Fire(refPart) ← truyền REF PART -- L1165: Prompt.Enabled = OwnerUserId ~= LocalPlayer.UserId ← mua rồi thì tắt prompt -- -- ── CON NÀO SPAWN HOANG ĐƯỢC (thế giới Main) ──────────────────────────────── -- Lọc bằng PetGating.IsAcquirable + PetData.SpawnChance > 0: -- Bunny 11.90% · Frog 11.90% · Owl 7.14% · Deer 4.29% · Robin 2.86% · Bee 2.38% -- Unicorn 0.71% · GoldenDragonfly 0.60% · Firefly 0.60% -- Raccoon 0.24% ← HIẾM NHẤT còn săn được ($5M) -- Bear 0.23% · BaldEagle 0.23% · Monkey 0.20% -- -- ── CON NÀO SPAWN HOANG ĐƯỢC (thế giới FallHarvest) ──────────────────────── -- Đo live 2026-08-06 ĐỨNG TẠI FallHarvest — bộ pet HOÀN TOÀN KHÁC Main, không con nào trùng: -- Hedgehog 1.00% $140K · Turkey 1.00% $100K · Dog 0.75% $50K -- Squirrel 0.60% $1.2M · Swan 0.40% $1.6M · Wolf 0.20% $6.5M -- Fox 0.10% $10M · ShadowDragon 0.00464% $30M ← hiếm nhất, nhưng CÓ spawn -- -- ★ ShadowDragon SPAWN HOANG ĐƯỢC ở world này (SpawnChance 0.00464, IsAcquirable=true). -- Ở Main thì vô vọng. Đây là lý do chính đáng để auto qua map. -- -- ⚠ ĐỌC SpawnChance phải xem SỐ THÔ, đừng format %.2f: 0.00464 hiện ra "0.00%" trông -- y hệt "không spawn được" trong khi thực tế là spawn được. -- -- ⚠ PetGating.IsAcquirable PHỤ THUỘC WORLD: đứng ở Main thì pet FallHarvest trả false. -- Nên phần kiểm tra Targets chỉ đúng SAU KHI đã qua map — đó là lý do Scan.start gọi -- World.ensure() TRƯỚC rồi mới rebuildTargets(). -- ============================================================================ local CONFIG = { -- ★ Mặc định: mọi con Mythic/Super còn săn được. -- Targets = {} → BÁO HẾT mọi con spawn ra, không lọc loài. Targets = { "Raccoon", "Bear", "BaldEagle", "Monkey", "Unicorn", "GoldenDragonfly", "Firefly" }, -- ★ TARGETS THEO WORLD — mỗi world có bộ pet spawn hoang RIÊNG, không giao nhau. -- Vào world nào thì lấy list của world đó; world không khai báo ở đây → dùng Targets ở trên. -- Đặt = {} cho một world nghĩa là world đó BÁO HẾT mọi con. TargetsByWorld = { Main = { "Raccoon", "Bear", "BaldEagle", "Monkey", "Unicorn", "GoldenDragonfly", "Firefly" }, -- Đo live 2026-08-06 ĐỨNG TẠI FallHarvest (place 126987765280963), SpawnChance THÔ: -- Hedgehog 1.0 · Turkey 1.0 · Dog 0.75 · Squirrel 0.6 ($1.2M) · Swan 0.4 ($1.6M) -- Wolf 0.2 ($6.5M) · Fox 0.1 ($10M) · ShadowDragon 0.00464 ($30M) -- ★ ShadowDragon SPAWN HOANG ĐƯỢC ở đây (0.00464%, IsAcquirable=true) — hiếm khủng -- khiếp (~1/21.500 lượt spawn) nhưng KHÁC 0. Ở Main thì nó = không bao giờ. FallHarvest = { "Fox", "Wolf", "Swan", "Squirrel", "ShadowDragon"}, }, -- ── AUTO QUA MAP EVENT ────────────────────────────────────────────────── -- Đi bằng ĐÚNG đường của game: Networking.Worlds.RequestTravel:Fire() -- (chính là thứ nút Teleport trong UI "Event Worlds" gọi — EventWorldsTeleporterController L303). -- Server nhận rồi tự teleport sang place của world. KHÔNG tự gọi TeleportService. World = { Enabled = true, -- false = ở nguyên world đang đứng Target = "FallHarvest", -- "FallHarvest" | "Main" | "" (rỗng = tắt) WaitReady = 20, -- chờ module Worlds/Networking nạp xong tối đa N giây Retries = 3, -- Fire xong mà TravelFailed / không nhúc nhích → thử lại mấy lần RetryGap = 8, -- nghỉ giữa 2 lần thử ScanWhileWaiting = true, -- vẫn quét trong lúc chờ server kéo đi (travel mất vài giây) }, -- ── LỌC THEO BIẾN THỂ ─────────────────────────────────────────────────── -- Con trong AlwaysLog: báo BẤT KỂ biến thể (kể cả Normal). -- Mọi con khác trong Targets: CHỈ báo khi là Big / Huge / Rainbow (và các ghép của chúng). -- ⚠ "Mega" là cách gọi ngoài đời — trong code game cỡ lớn nhất tên là **Huge** -- (PetSizes.Scales = {Big=2, Huge=4}). Không có định danh "Mega" nào. VariantFilter = { Enabled = true, AlwaysLog = { "ShadowDragon" }, -- con nào cũng báo, Normal cũng báo -- Biến thể được tính là "đáng báo". Bỏ bớt nếu chỉ muốn Huge chẳng hạn. Accept = { Big = true, Huge = true, Rainbow = true, BigRainbow = true, HugeRainbow = true }, }, ScanSeconds = 3, -- quét lại toàn bộ mỗi N giây (lưới chắn cho ChildAdded) ReportRepeat = false, -- false = mỗi con chỉ báo 1 lần ShowOnStart = true, -- lúc chạy in mọi con đang có (để biết scanner sống) SkipOwned = true, -- bỏ qua con đã có người mua (OwnerUserId ~= 0) -- Discord webhook — chỉ bắn con TRÚNG Targets (Scan.report mới gọi), nên không spam. Webhook = "https://discord.com/api/webhooks/1442460229018321008/EXWgAgDDxIb8p0sEkCH_m6AAwFkwEMEPL6s9FbEdDnHqp3U5Bm_MCN0mV8iK7MVvjx_y", WebhookPing = "", -- "<@&ID>" tag role, hoặc "@everyone". Để trống = không tag. WebhookMinPrice = 0, -- chỉ bắn con giá >= N (0 = bắn hết những con đã trúng Targets) -- ── HIỆU NĂNG: cap fps thấp + hạ đồ hoạ, bùng fps khi có việc ──────────── -- Bot không cần nhìn. Chạy thường trực ở FpsBase (rẻ CPU), gặp việc gấp thì -- bùng lên FpsBoost vài giây rồi tự về — giống sab_scanner2. -- ⚠ Ở fps 10 mỗi frame là 100ms, nên vòng "đợi attribute" trong ChildAdded (12 frame) -- sẽ mất tới 1.2s thay vì 0.2s. Vì thế ChildAdded LUÔN boost trước khi đọc — -- pet ở đây có Lifetime ngắn (đo được con Swan chỉ còn 8s), chậm là mất. Perf = { Enabled = true, FpsBase = 10, -- cap thường trực FpsBoost = 10, -- bùng khi có việc (0 hoặc <= FpsBase = tắt boost) BoostSec = 3, -- giữ boost N giây sau hoạt động gần nhất (có việc tiếp thì gia hạn) BoostOnFind = true, -- thấy pet / có pet mới spawn → boost BoostOnHop = true, -- lúc travel + teleport → boost cho nhanh LowSpec = true, -- ★ TẦNG AN TOÀN: chỉ đụng setting CLIENT (QualityLevel, Lighting effect) Mute = true, -- MasterVolume = 0 (setting người dùng, không phải property object) -- ★ TẦNG NẶNG (mặc định TẮT): duyệt cả workspace ghi property lên object GAME để -- xoá texture/decal/particle. Chưa khảo sát GAG2 có anti-cheat quét property hay không. -- StripWorld bên dưới đã ăn đứt nó về RAM nên thường KHÔNG cần bật cái này. LowHeavy = false, -- ★★ TẮT HẲN 3D RENDER — GPU về ~0. ĐO LIVE 2026-08-06: GPU 2.4 → 0.05, -- GraphicsParts 105 → 9 MB. Scan VẪN chạy (WildPetRef là data replicate, không -- dính gì tới render; StreamingEnabled stream theo VỊ TRÍ nhân vật chứ không theo render). -- ⚠ ĐÁNH ĐỔI: màn hình ĐEN, không nhìn thấy gì. Muốn nhìn thì đổi false. NoRender = true, -- ★★ XOÁ CỤC BỘ nhánh workspace mà scan không cần. ĐÒN BẨY LỚN NHẤT ở game này: -- workspace có 138.431 instance, riêng Gardens chiếm 131.035 (95%) — toàn cây/quả -- của 8 người chơi, hoàn toàn vô can với việc quét pet hoang. -- ĐO LIVE trước/sau khi xoá: -- instance 80.497 → 6.632 RAM 3349 → 3017 MB -- Luau thread 16.400 → 7.197 GcTime 0.418 → 0.166 (GC chạy trên main thread!) -- BaseParts 162 → 59 MB · GraphicsParts 105 → 9 MB · Signals 64 → 19 MB -- Xoá phía CLIENT không replicate lên server; rejoin là về như cũ. -- ⚠ Vườn của chính bạn cũng biến mất khỏi màn hình (chỉ local). StripWorld = true, StripList = { "Gardens", "_Gardens", "_PetVisualClient", "PottedPlantVisuals", "GnomeVisuals", "BirdVisuals", "GrapplingHookVisuals", "Presents", "DroppedItems", "RainDrops", "RainSplashes", "LightningEffects", "StormRainDrops", "StormSplashes", "BlizzardBeams" }, StripEvery = 60, -- quét lại mỗi N giây: StreamingEnabled CÓ THỂ stream về khi di chuyển -- (đo lúc đứng yên thì không về, nhưng đừng tin chắc). 0 = chỉ xoá 1 lần. }, -- ── HOP (port từ sab_scanner.lua) — CHẠY ĐÚNG 1 LẦN mỗi lần execute ── -- Vào server → post presence (init) → get usernames → thấy acc fleet nào đang ở đây -- thì mình là người tới SAU nên mình đi. Không có vòng canh, không có hop-vì-ở-lâu. Hop = { Enabled = true, -- false = không bao giờ hop, chỉ đứng scan JoinDelay = 5, -- chờ N giây sau khi vào để Players:GetPlayers() nạp đủ rồi mới xét MaxPages = 6, -- số trang gọi API server-list MaxRetries = 12, -- thử tối đa mấy jobId trước khi bỏ cuộc WaitPerTry = 6, -- chờ mỗi lần teleport (đợi TeleportInitFailed nếu full) RetryBackoff = 1.5, Matchmaking = true, -- retry hết vẫn full → Teleport(placeId) cho Roblox tự xếp NoServerWait = 60, -- API trả rỗng → nghỉ bao lâu rồi thử lại NoServerMax = 3, -- rỗng đủ N lần → hop random ScriptUrl = "", -- URL raw script này → tự chạy lại sau hop (queue_on_teleport) UseHobeojob = true, -- nguồn server ưu tiên (đã lọc server còn slot); rỗng → fallback API Roblox PostBlacklist = true, -- POST jobId ĐÍCH vào blacklist NGAY TRƯỚC khi teleport (reserve cho fleet) ReportDead = true, -- teleport ra GameEnded = server chết → POST gỡ khỏi pool }, -- ── FLEET (port sab_scanner) ── -- ⚠ KHÔNG có check "poor" — bên GAG2 không cần, chạy như bình thường. Presence = { Enabled = true, -- POST heartbeat {account,placeId,jobId,players,ping,hops,uptime} Interval = 60, -- post ngay khi vào sv, rồi mỗi N giây }, FriendRegistry = { Enabled = true, GetTries = 4, -- list fleet đông → host chậm; 1 GET fail = registry rỗng = mù clone GetRetryGap = 3, CacheTTL = 30, -- ★ cache 30s, dùng chung MỌI TAB trên máy (file đĩa) -- → cả máy chỉ GET ~1 lần/30s, dù mở bao nhiêu tab FailBackoff = 30, -- GET hỏng → lùi lịch thử lại chừng này giây -- Bầu leader: chỉ 1 acc trong CẢ MÁY được gọi mạng mỗi cửa sổ TTL, số còn lại đọc file. LockTTL = 60, -- khoá sống bao lâu (PHẢI > thời gian 1 lượt GET chậm nhất) LockSettle = 1, -- chờ ĐỒNG NHẤT sau khi ghi khoá rồi mới xác nhận FollowerWait = 10, -- follower đợi leader ghi file tối đa chừng này giây MaxBytes = 2000000, -- payload lớn hơn thì BỎ (endpoint trả cả bảng người chơi của place) -- (GraceSeconds/StaggerSeconds/RefreshInterval ĐÃ BỎ: chúng phục vụ mô hình canh-định-kỳ. -- Với one-shot lúc join thì acc tới sau tự nhiên là acc thấy acc trước → đi ngay, -- không cần grace để phân xử ai đi.) }, } local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local HttpService = game:GetService("HttpService") local CollectionService = game:GetService("CollectionService") local LocalPlayer = Players.LocalPlayer -- ★ LUÔN lấy player qua hàm này, ĐỪNG index thẳng LocalPlayer. -- Players.LocalPlayer là NIL khi script chạy sớm (autoexec chạy trước lúc player join) -- và cả trong lúc teleport. Biến chụp ở trên khi đó nil VĨNH VIỄN vì không ai lấy lại -- → "attempt to index nil with 'Character'". Hàm này thử lại mỗi lần gọi. local function me() local p = Players.LocalPlayer if p then LocalPlayer = p ; return p end return LocalPlayer end -- ── HTTP (đặt SỚM: Scan.webhook ở dưới cần, mà local khai sau thì hàm trên không thấy) ── local httpRequest = (syn and syn.request) or (http and http.request) or http_request or (fluxus and fluxus.request) or request local function httpGet(url) -- httpRequest TRƯỚC: có User-Agent nên ít bị Cloudflare làm chậm hơn game:HttpGet if httpRequest then local ok, r = pcall(httpRequest, { Url = url, Method = "GET" }) if ok and r and r.Body then local code = tonumber(r.StatusCode) if not code or (code >= 200 and code < 300) then return r.Body end end end local ok2, body = pcall(function() return game:HttpGet(url) end) return ok2 and body or nil end local function httpPost(url, body) if not httpRequest then return false end local ok = pcall(httpRequest, { Url = url, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = body, }) return ok end -- ── FPS: cap thấp thường trực, bùng khi có việc rồi tự về ─────────────────── -- Deadline-based: mỗi lần boost(N) đẩy mốc hết hạn ra xa; chỉ MỘT thread canh mốc đó, -- nên gọi boost() dồn dập cũng không đẻ thêm thread. local Fps = { untilAt = 0, boosted = false } function Fps.cap(n) if type(setfpscap) == "function" then pcall(setfpscap, n) end end function Fps.setBase() local P = CONFIG.Perf or {} if P.Enabled == false then return end Fps.cap(P.FpsBase or 10) end function Fps.boost(seconds) local P = CONFIG.Perf or {} if P.Enabled == false then return end local base, hi = P.FpsBase or 10, P.FpsBoost or 60 if hi <= base then return end -- boost <= base = tắt boost Fps.untilAt = math.max(Fps.untilAt, os.clock() + (tonumber(seconds) or P.BoostSec or 3)) if Fps.boosted then return end -- đã boost rồi thì chỉ gia hạn mốc Fps.boosted = true Fps.cap(hi) task.spawn(function() while os.clock() < Fps.untilAt do task.wait(0.1) end Fps.boosted = false Fps.cap(base) end) end -- ── LOW SPEC: hạ tải CPU/RAM ──────────────────────────────────────────────── -- Tầng 1 (mặc định BẬT) chỉ đụng setting của CLIENT — không ghi gì lên object game. -- Tầng 2 (LowHeavy, mặc định TẮT) mới ghi property lên object game. -- CỐ Ý KHÔNG port 2 thứ từ lowercpu.lua: đặt Transparency=1 cho cả workspace (đúng chữ ký -- wallhack/ESP, lại không nhìn thấy gì để theo dõi bot) và vòng getnilinstances(). local Low = { applied = false } function Low.strip(v) if v:IsA("BasePart") and not v:IsA("MeshPart") then v.Material, v.Reflectance = Enum.Material.Plastic, 0 elseif v:IsA("Decal") or v:IsA("Texture") then v.Transparency = 1 elseif v:IsA("ParticleEmitter") or v:IsA("Trail") then v.Lifetime = NumberRange.new(0) elseif v:IsA("Fire") or v:IsA("SpotLight") or v:IsA("Smoke") or v:IsA("Sparkles") then v.Enabled = false elseif v:IsA("MeshPart") then v.Material, v.Reflectance = Enum.Material.Plastic, 0 v.TextureID = "" elseif v:IsA("SpecialMesh") then v.TextureId = "" elseif v:IsA("ShirtGraphic") then v.Graphic = "" elseif v:IsA("Shirt") then v.ShirtTemplate = "" elseif v:IsA("Pants") then v.PantsTemplate = "" end end -- Nhánh KHÔNG BAO GIỜ được xoá, kể cả khi bị nhét vào StripList. -- Map = chứa WildPetRef — xoá là mù hẳn, mất luôn lý do tồn tại của script. -- Terrain/Camera = xoá thì client hỏng. -- Tên nhân vật mình = giữ để Scan.read còn tính được khoảng cách. local STRIP_NEVER = { Map = true, Terrain = true, Camera = true } function Low.stripWorld() local P = CONFIG.Perf or {} if P.Enabled == false or not P.StripWorld then return 0 end local selfName = (me() and me().Name) or "" local n = 0 for _, name in ipairs(P.StripList or {}) do if not STRIP_NEVER[name] and name ~= selfName then local f = workspace:FindFirstChild(name) if f then -- Xoá từng con thay vì cả folder: giữ lại folder rỗng để script của game -- còn FindFirstChild ra được, khỏi lỗi liên tục (lỗi spam còn tốn CPU hơn). for _, c in ipairs(f:GetChildren()) do if pcall(function() c:Destroy() end) then n = n + 1 end end end end end return n end function Low.apply() local P = CONFIG.Perf or {} if Low.applied or P.Enabled == false then return end Low.applied = true -- tắt 3D render trước: rẻ nhất, hiệu quả ngay, không đụng DataModel if P.NoRender then local okR = pcall(function() game:GetService("RunService"):Set3dRenderingEnabled(false) end) print(("[wildpet] 3D render: %s"):format(okR and "TAT (man hinh den, GPU ~0)" or "khong tat duoc")) end -- xoá nhánh thừa + canh lại định kỳ phòng khi streaming kéo về if P.StripWorld then local n = Low.stripWorld() print(("[wildpet] strip world: xoa %d nhanh · workspace con %d instance") :format(n, #workspace:GetDescendants())) local every = tonumber(P.StripEvery) or 0 if every > 0 then task.spawn(function() while true do task.wait(every) local k = Low.stripWorld() if k > 0 then print(("[wildpet] strip world: don them %d nhanh moi stream ve"):format(k)) end end end) end end if P.LowSpec == false then return end -- Tầng 1 — setting client, không rủi ro pcall(function() settings().Rendering.QualityLevel = Enum.QualityLevel.Level01 end) pcall(function() UserSettings():GetService("UserGameSettings").SavedQualityLevel = Enum.SavedQualitySetting.QualityLevel1 end) if P.Mute ~= false then pcall(function() UserSettings():GetService("UserGameSettings").MasterVolume = 0 end) end local L = game:GetService("Lighting") pcall(function() L.GlobalShadows = false end) pcall(function() L.FogEnd = 9e9 end) for _, e in ipairs(L:GetChildren()) do if e:IsA("BlurEffect") or e:IsA("SunRaysEffect") or e:IsA("ColorCorrectionEffect") or e:IsA("BloomEffect") or e:IsA("DepthOfFieldEffect") then pcall(function() e.Enabled = false end) end end if not P.LowHeavy then print(("[wildpet] low-spec: QualityLevel=Level01%s · fps cap %d") :format(P.Mute ~= false and " + tat tieng" or "", P.FpsBase or 10)) return end -- Tầng 2 — ghi property lên object game. NHẢ FRAME mỗi 400 object: map này vài chục -- nghìn instance, duyệt một hơi là đóng băng client mấy chục giây. local n, all = 0, workspace:GetDescendants() for i, v in ipairs(all) do if pcall(Low.strip, v) then n = n + 1 end if i % 400 == 0 then task.wait() end end workspace.DescendantAdded:Connect(function(v) task.defer(function() pcall(Low.strip, v) end) end) print(("[wildpet] low-spec NANG: da ha %d object + theo doi object moi"):format(n)) end -- Cap fps NGAY lúc load (rẻ, không yield) — Low.apply có yield nên để Scan.start spawn. Fps.setBase() local Scan = { seen = {}, hits = 0, scans = 0, running = false, conn = nil } -- ── WORLD / AUTO QUA MAP EVENT ────────────────────────────────────────────── -- Khảo sát live 2026-08-06 (Grow a Garden 2, Main place 97598239454123): -- ReplicatedStorage.SharedModules.Worlds → .CurrentId, .Worlds{Main,FallHarvest}, .GetPlaceId -- ReplicatedStorage.SharedModules.Networking → .Worlds.RequestTravel / TravelStarted / TravelFailed -- ReplicatedStorage.SharedModules.WorldRelease → .SecondsUntilWorldCloses(id) -- PlaceId: Main 97598239454123 · FallHarvest 126987765280963 -- (bot 73504898027860 / 129343810645058 · hunt 112469282445074 / 137395689498699) -- -- ĐI BẰNG ĐÚNG ĐƯỜNG CỦA GAME: RequestTravel:Fire(id) — y hệt nút Teleport trong UI -- "Event Worlds" (EventWorldsTeleporterController L303). Server tự teleport mình sang. -- KHÔNG tự gọi TeleportService: place của world là reserved-ish, teleport tay dễ bị đá về. local World = { mod = nil, net = nil, rel = nil, travelling = false, lastErr = nil } function World.load(waitSec) if World.mod and World.net then return true end local deadline = os.clock() + (tonumber(waitSec) or 20) local sm repeat sm = ReplicatedStorage:FindFirstChild("SharedModules") if sm and sm:FindFirstChild("Worlds") and sm:FindFirstChild("Networking") then break end task.wait(0.2) until os.clock() >= deadline if not (sm and sm:FindFirstChild("Worlds") and sm:FindFirstChild("Networking")) then World.lastErr = "khong thay SharedModules.Worlds/Networking" return false end World.mod = select(2, pcall(require, sm.Worlds)) World.net = select(2, pcall(require, sm.Networking)) if sm:FindFirstChild("WorldRelease") then World.rel = select(2, pcall(require, sm.WorldRelease)) end if type(World.mod) ~= "table" then World.mod = nil end if type(World.net) ~= "table" then World.net = nil end if type(World.rel) ~= "table" then World.rel = nil end if not (World.mod and World.net) then World.lastErr = "require Worlds/Networking that bai" return false end return true end -- id world đang đứng. Ưu tiên Worlds.CurrentId; module chưa nạp thì dò ngược từ PlaceId. function World.id() if World.mod and type(World.mod.CurrentId) == "string" then return World.mod.CurrentId end if World.mod and type(World.mod.Worlds) == "table" then for id in pairs(World.mod.Worlds) do local ok, pid = pcall(World.mod.GetPlaceId, id) if ok and pid == game.PlaceId then return id end end end return "" end function World.displayName(id) local w = World.mod and World.mod.Worlds and World.mod.Worlds[id] return (w and w.DisplayName) or id or "?" end -- world còn mở không (event có hạn — FallHarvest đo được 56 ngày lúc khảo sát) function World.secondsLeft(id) if not (World.rel and World.rel.SecondsUntilWorldCloses) then return nil end local ok, s = pcall(World.rel.SecondsUntilWorldCloses, id) return ok and tonumber(s) or nil end -- bắn 1 phát RequestTravel. true = đã gửi (KHÔNG có nghĩa là đã tới nơi). function World.fire(id) if not (World.net and World.net.Worlds and World.net.Worlds.RequestTravel) then World.lastErr = "khong co Networking.Worlds.RequestTravel" return false end local ok, err = pcall(function() World.net.Worlds.RequestTravel:Fire(id) end) if not ok then World.lastErr = tostring(err) end return ok end -- Nối TravelStarted/TravelFailed để biết server có nhận hay không (thay vì đoán mò). function World.hookEvents() if World.hooked or not (World.net and World.net.Worlds) then return end World.hooked = true local W = World.net.Worlds pcall(function() W.TravelStarted.OnClientEvent:Connect(function(...) World.travelling = true print("[wildpet][world] TravelStarted — server dang keo minh di") end) end) pcall(function() W.TravelFailed.OnClientEvent:Connect(function(reason) World.travelling = false World.lastErr = tostring(reason) warn("[wildpet][world] TravelFailed: " .. tostring(reason)) end) end) end -- Đi tới world đích nếu chưa ở đó. Trả về true nếu ĐÃ Ở SẴN (khỏi đi) hoặc đã gửi được lệnh. function World.ensure() local cfg = CONFIG.World or {} local want = tostring(cfg.Target or "") if cfg.Enabled == false or want == "" then return true, "tat" end if not World.load(cfg.WaitReady) then warn("[wildpet][world] khong nap duoc module: " .. tostring(World.lastErr) .. " → o lai world hien tai") return false, World.lastErr end World.hookEvents() local cur = World.id() if cur == want then print(("[wildpet][world] dang o %s (%s) roi → khong can di"):format(want, World.displayName(want))) return true, "da o day" end if not (World.mod.Worlds and World.mod.Worlds[want]) then warn(("[wildpet][world] world '%s' khong ton tai trong Worlds → bo qua"):format(want)) return false, "world la" end local left = World.secondsLeft(want) if left ~= nil and left <= 0 then warn(("[wildpet][world] '%s' DA DONG → o lai %s"):format(want, cur)) return false, "world dong" end -- travel = server kéo mình sang place khác; vòng chờ bên dưới đếm frame nên fps 10 làm chậm if (CONFIG.Perf or {}).BoostOnHop ~= false then Fps.boost((tonumber(cfg.Retries) or 3) * (tonumber(cfg.RetryGap) or 8) + 10) end print(("[wildpet][world] dang o %s → xin qua %s (%s)%s"):format( cur == "" and "?" or cur, want, World.displayName(want), left and (", con %.1f ngay"):format(left / 86400) or "")) -- Fire xong thì server teleport → script này chết theo. Còn sống sau RetryGap = chưa đi được. local tries = math.max(1, tonumber(cfg.Retries) or 3) for i = 1, tries do World.travelling = false World.lastErr = nil if not World.fire(want) then warn(("[wildpet][world] Fire loi (%d/%d): %s"):format(i, tries, tostring(World.lastErr))) end local gap = tonumber(cfg.RetryGap) or 8 local t0 = os.clock() while os.clock() - t0 < gap do task.wait(0.5) if World.id() == want then return true, "toi noi" end end if i < tries then warn(("[wildpet][world] chua di duoc sau %ds (%s) → thu lai %d/%d"):format( gap, tostring(World.lastErr or "khong phan hoi"), i + 1, tries)) end end warn(("[wildpet][world] khong qua duoc %s sau %d lan → SCAN TAM tai %s"):format(want, tries, cur)) return false, "het luot" end -- ── dữ liệu game ──────────────────────────────────────────────────────────── local PetData, PetGating do local sd = ReplicatedStorage:WaitForChild("SharedData", 10) if sd then PetData = select(2, pcall(require, sd:WaitForChild("PetData", 5))) end local sm = ReplicatedStorage:FindFirstChild("SharedModules") if sm and sm:FindFirstChild("PetGating") then PetGating = select(2, pcall(require, sm.PetGating)) end if type(PetData) ~= "table" then PetData = nil end if type(PetGating) ~= "table" then PetGating = nil end end local function norm(s) return tostring(s or ""):lower():gsub("[%s_%-]", "") end -- tra loài: dựng SẴN 1 lần, nhận cả mã loài lẫn DisplayName local BY_NORM = {} if PetData then for k, v in pairs(PetData) do if type(v) == "table" then BY_NORM[norm(k)] = { v, k } if v.DisplayName then BY_NORM[norm(v.DisplayName)] = { v, k } end end end end local function speciesInfo(name) local e = BY_NORM[norm(name)] if e then return e[1], e[2] end end -- ★ Target list bám theo WORLD: mỗi world một bộ pet spawn hoang riêng, không giao nhau. -- Phải dựng LẠI sau khi qua map, nếu không sẽ đứng ở Fall Harvest mà rình pet của Main -- (Raccoon/Bear/… không bao giờ spawn bên đó) = quét mãi không ra gì. local WANT, WANT_ALL, ACTIVE = {}, false, {} local function rebuildTargets() local id = World.id() local list = (CONFIG.TargetsByWorld and CONFIG.TargetsByWorld[id]) or CONFIG.Targets or {} ACTIVE, WANT = list, {} for _, t in ipairs(list) do WANT[norm(t)] = true local _, key = speciesInfo(t) if key then WANT[norm(key)] = true end end -- list rỗng → BÁO HẾT, không lọc loài nào. WANT_ALL = (#list == 0) return id, list end rebuildTargets() local function isWanted(sp) if WANT_ALL then return true end return WANT[norm(sp)] == true end -- ★ %.2f LÀM MẤT con hiếm nhất: ShadowDragon SpawnChance 0.00464 in ra "0.00%" trông y hệt -- "không spawn được". Chance nhỏ hơn 0.01 thì in đủ số để nhìn ra nó KHÁC 0. local function pct(n) n = tonumber(n) or 0 if n > 0 and n < 0.01 then return ("%.5f%%"):format(n) end return ("%.2f%%"):format(n) end local function money(n) n = tonumber(n) or 0 if n >= 1e9 then return ("%.2fB"):format(n / 1e9) end if n >= 1e6 then return ("%.2fM"):format(n / 1e6) end if n >= 1e3 then return ("%.1fK"):format(n / 1e3) end return tostring(math.floor(n)) end -- ── đọc 1 con ─────────────────────────────────────────────────────────────── function Scan.folder() local map = workspace:FindFirstChild("Map") return map and map:FindFirstChild("WildPetRef") or nil end -- ── BIẾN THỂ (Big / Huge / Rainbow) ───────────────────────────────────────── -- Khảo sát live 2026-08-07, SpawnPetController hàm dựng wild pet (line 626) có đúng các -- hằng: GetAttribute · PetSize · Big · Huge · BigScale · HugeScale · ScaleTo -- · PetType · Rainbow · PetRainbow · AddTag -- ⇒ Pet hoang CÓ biến thể: server ghi attribute PetSize/PetType lên ref part, client đọc -- rồi phóng to model (ScaleTo) và gắn tag "PetRainbow" để PetRainbowController tô cầu vồng. -- -- ⚠ Attribute nil thì Roblox KHÔNG liệt kê trong GetAttributes() — con Normal trông y hệt -- "không có trường nào". Đừng nhìn một server toàn Normal rồi kết luận game không có biến thể. -- -- ⚠ CHƯA XÁC MINH ĐƯỢC LIVE: mọi server tôi đứng đều 100% Normal, không có con Big/Huge/ -- Rainbow nào để đối chiếu. Nên đọc PHÒNG THỦ: thử ref part → model hiển thị → tag. -- Ba nguồn, trúng nguồn nào cũng ra. local SIZE_TAGS = { PetRainbow = true, RainowPet = true, RainbowModel = true } -- "RainowPet" THIẾU chữ b — game gõ sai, giữ nguyên local function normSize(v) if v == nil then return nil end local s = tostring(v):lower() if s == "big" then return "Big" end if s == "huge" then return "Huge" end return nil end -- tìm model hiển thị tương ứng ref part (WildPetSpawns đặt tên "WildPet__") local function visualOf(part) local map = workspace:FindFirstChild("Map") local sp = map and map:FindFirstChild("WildPetSpawns") if not sp then return nil end local rn = part.Name for _, m in ipairs(sp:GetChildren()) do if m.Name:find(rn, 1, true) then return m end end return nil end -- trả về size ("Big"/"Huge"/nil), rainbow (bool), variant ("Normal"/"Big"/"HugeRainbow"/…) local function readVariant(part) local size = normSize(part:GetAttribute("PetSize")) local rb = tostring(part:GetAttribute("PetType") or "") == "Rainbow" if not size or not rb then local m = visualOf(part) if m then size = size or normSize(m:GetAttribute("PetSize")) if not rb then rb = tostring(m:GetAttribute("PetType") or "") == "Rainbow" end if not rb then for _, t in ipairs(CollectionService:GetTags(m)) do if SIZE_TAGS[t] then rb = true break end end end -- chốt cuối cho size: model đã bị ScaleTo(BigScale/HugeScale). Chỉ tin khi lệch RÕ, -- vì scale gốc mỗi loài mỗi khác — đừng đoán bừa từ số lẻ. if not size then local ok, s = pcall(function() return m:GetScale() end) if ok and type(s) == "number" then if s >= 3.5 then size = "Huge" elseif s >= 1.8 then size = "Big" end end end end end -- ghép lớp y hệt RarePetAnnounceData.GetVariantClass local variant if size == "Huge" then variant = rb and "HugeRainbow" or "Huge" elseif size == "Big" then variant = rb and "BigRainbow" or "Big" else variant = rb and "Rainbow" or "Normal" end return size, rb, variant end function Scan.read(part) if not (part and part.Parent) then return nil end local sp = part:GetAttribute("PetName") if not sp then return nil end local size, rainbow, variant = readVariant(part) local info = { species = sp, id = part.Name:match("^WildPet_(.+)$") or part.Name, rarity = part:GetAttribute("Rarity"), price = tonumber(part:GetAttribute("Price")), owner = tonumber(part:GetAttribute("OwnerUserId")) or 0, state = part:GetAttribute("State"), size = size, rainbow = rainbow, variant = variant, part = part, } -- còn sống bao lâu = SpawnedAt + Lifetime − server-time hiện tại local at, life = tonumber(part:GetAttribute("SpawnedAt")), tonumber(part:GetAttribute("Lifetime")) if at and life then info.left = (at + life) - workspace:GetServerTimeNow() end if part:IsA("BasePart") then info.pos = part.Position local p = me() local ch = p and p.Character local hrp = ch and ch:FindFirstChild("HumanoidRootPart") if hrp then info.dist = (part.Position - hrp.Position).Magnitude end end return info end function Scan.line(info) local sp = speciesInfo(info.species) local b = { info.species } if info.variant and info.variant ~= "Normal" then b[#b + 1] = ("<<%s>>"):format(info.variant) end b[#b + 1] = ("[%s]"):format(tostring(info.rarity or (sp and sp.Rarity) or "?")) b[#b + 1] = ("gia %s"):format(money(info.price or (sp and sp.BasePrice))) if info.left then b[#b + 1] = ("con %ds"):format(math.max(0, math.floor(info.left))) end if info.dist then b[#b + 1] = ("cach %.0f"):format(info.dist) end if info.owner and info.owner ~= 0 then b[#b + 1] = ("DA MUA boi %d"):format(info.owner) end if info.state then b[#b + 1] = tostring(info.state) end return table.concat(b, " · ") end -- màu theo GIÁ (giá đáng tin hơn Rarity: Rarity là chuỗi tự do của game) local function embedColor(price) price = tonumber(price) or 0 if price >= 10e6 then return 0xE74C3C end -- đỏ — Fox $10M, ShadowDragon $30M if price >= 1e6 then return 0xE67E22 end -- cam — Wolf/Swan/Squirrel if price >= 1e5 then return 0x3498DB end -- xanh return 0x95A5A6 -- xám end -- ★ Discord chặn 30 request/phút mỗi webhook. Pet spawn dồn có thể vượt → gửi QUA HÀNG ĐỢI, -- 1 request mỗi WH_GAP giây, gặp 429 thì đọc retry_after mà đợi đúng chừng đó. local WH = { q = {}, running = false, gap = 2, sent = 0, dropped = 0 } function WH.pump() if WH.running then return end WH.running = true task.spawn(function() while #WH.q > 0 do local body = table.remove(WH.q, 1) local tries = 0 while tries < 4 do tries = tries + 1 local ok, r = pcall(httpRequest, { Url = CONFIG.Webhook, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = body, }) local code = ok and r and tonumber(r.StatusCode) or 0 if code >= 200 and code < 300 then WH.sent = WH.sent + 1 break end if code == 429 then -- Discord trả retry_after (giây). Không parse được thì lùi 5s. local wait = 5 local okj, d = pcall(function() return HttpService:JSONDecode(r.Body) end) if okj and type(d) == "table" and tonumber(d.retry_after) then wait = math.min(tonumber(d.retry_after) + 0.5, 60) end warn(("[wildpet][wh] 429 rate-limit -> doi %.1fs"):format(wait)) task.wait(wait) else if tries >= 4 then WH.dropped = WH.dropped + 1 warn(("[wildpet][wh] gui that bai (HTTP %s) sau %d lan -> bo"):format(tostring(code), tries)) else task.wait(2) end end end task.wait(WH.gap) end WH.running = false end) end function Scan.webhook(info) if not CONFIG.Webhook or CONFIG.Webhook == "" or not httpRequest then return end if (tonumber(info.price) or 0) < (tonumber(CONFIG.WebhookMinPrice) or 0) then return end local sp = speciesInfo(info.species) local rarity = tostring(info.rarity or (sp and sp.Rarity) or "?") local price = tonumber(info.price) or (sp and tonumber(sp.BasePrice)) or 0 local wid = World.id() local left = info.left and math.max(0, math.floor(info.left)) or nil local variant = info.variant or "Normal" local fields = { { name = "Bien the", value = (variant ~= "Normal") and ("**" .. variant .. "**") or "Normal", inline = true }, { name = "Gia", value = ("¢%s"):format(money(price)), inline = true }, { name = "Do hiem", value = rarity, inline = true }, { name = "Con lai", value = left and (left .. "s") or "?", inline = true }, { name = "World", value = ("%s (%s)"):format(World.displayName(wid), wid ~= "" and wid or "?"), inline = true }, { name = "PlaceId", value = ("`%d`"):format(game.PlaceId), inline = true }, { name = "Players", value = ("%d"):format(#Players:GetPlayers()), inline = true }, -- JobId để riêng 1 dòng cho dễ copy (chuỗi 36 ký tự) { name = "JobId", value = ("```%s```"):format(tostring(game.JobId)), inline = false }, -- dán thẳng vào executor là vào đúng server đang có con này { name = "Vao thang", value = ("```lua\ngame:GetService(\"TeleportService\"):TeleportToPlaceInstance(%d,\"%s\")```") :format(game.PlaceId, tostring(game.JobId)), inline = false }, } if info.owner and info.owner ~= 0 then fields[#fields + 1] = { name = "⚠", value = ("DA CO NGUOI MUA (uid %d)"):format(info.owner), inline = false } end local content = CONFIG.WebhookPing if content == nil or content == "" then content = nil end WH.q[#WH.q + 1] = HttpService:JSONEncode({ content = content, embeds = {{ title = ("🐾 %s%s — ¢%s"):format( (variant ~= "Normal") and (variant .. " ") or "", tostring(info.species), money(price)), description = ("Scan bởi `%s`"):format(tostring(me() and me().Name or "?")), color = embedColor(price), fields = fields, footer = { text = ("gag2_petscan · %s"):format(os.date("%H:%M:%S")) }, }}, }) WH.pump() end -- ShadowDragon → luôn báo. Con khác → chỉ báo khi có biến thể. local ALWAYS = {} for _, n in ipairs((CONFIG.VariantFilter or {}).AlwaysLog or {}) do ALWAYS[norm(n)] = true end local function passVariant(info) local vf = CONFIG.VariantFilter or {} if vf.Enabled == false then return true end if ALWAYS[norm(info.species)] then return true end local _, key = speciesInfo(info.species) if key and ALWAYS[norm(key)] then return true end return (vf.Accept or {})[info.variant or "Normal"] == true end function Scan.report(info) if CONFIG.SkipOwned and info.owner and info.owner ~= 0 then return false end if not passVariant(info) then return false end if (not CONFIG.ReportRepeat) and Scan.seen[info.id] then return false end Scan.seen[info.id] = true Scan.hits = Scan.hits + 1 -- trúng mục tiêu → giữ fps cao thêm lúc nữa cho webhook + burst pet tiếp theo if (CONFIG.Perf or {}).BoostOnFind ~= false then Fps.boost() end print(("[wildpet] ★ %s"):format(Scan.line(info))) -- IN TRƯỚC if CONFIG.Webhook ~= "" then task.spawn(Scan.webhook, info) end return true end -- ── quét ──────────────────────────────────────────────────────────────────── function Scan.sweep(showAll) local f = Scan.folder() if not f then if showAll then print("[wildpet] chua co workspace.Map.WildPetRef — chua vao map?") end return 0, 0 end Scan.scans = Scan.scans + 1 local hit, all = 0, {} for _, p in ipairs(f:GetChildren()) do local info = Scan.read(p) if info then all[#all + 1] = ("%s(%s%s)"):format(info.species, money(info.price), (info.owner ~= 0) and ",da mua" or "") if isWanted(info.species) and Scan.report(info) then hit = hit + 1 end end end if showAll then print(("[wildpet] dang co %d con: %s"):format(#all, #all > 0 and table.concat(all, ", ") or "(khong con nao)")) end return hit, #all end -- ── HOP SERVER (port sab_scanner.lua: fetchRobloxApi + hop) ───────────────── -- BỎ so với bản gốc: hobeojob (backend riêng của SAB), blacklistJob/reportDead -- (cũng backend SAB). Giữ nguyên phần cốt lõi: paginate API Roblox với -- excludeFullGames, loại server hiện tại + đã thử, shuffle rồi thử lần lượt, -- GameFull thì đổi jobId, hết đường thì fallback matchmaking. local TeleportService = game:GetService("TeleportService") local rng = Random.new() local Hop = { visited = {}, noSvRetries = 0, noSvNextTry = 0, failed = false, failResult = nil } TeleportService.TeleportInitFailed:Connect(function(plr, result, msg) if plr ~= me() then return end Hop.failed = true Hop.failResult = result warn(("[wildpet] TeleportInitFailed: %s %s"):format(tostring(result), tostring(msg))) end) -- ── BACKEND hobeojob (mọi endpoint đều nhận placeId → dùng được cho GAG2) ── local HOBEO = "https://hobeojob.com" -- nguồn 1: hobeojob (đã lọc server còn slot) function Hop.fetchHobeojob() if not CONFIG.Hop.UseHobeojob then return nil end local raw = httpGet(("%s/api/jobs/%d"):format(HOBEO, game.PlaceId)) if not raw then return nil end local ok, data = pcall(function() return HttpService:JSONDecode(raw) end) if not (ok and data and data.servers) then return nil end local list = {} for _, s in ipairs(data.servers) do list[#list + 1] = { job = s.job_id, playing = tonumber(s.playing) or 0 } end return list end -- RESERVE: post jobId ĐÍCH vào blacklist NGAY TRƯỚC khi teleport → clone khác không nhảy trùng. -- CHỈ gọi lúc hop, KHÔNG post định kỳ khi đang ở server. function Hop.blacklistJob(jobId) if not (CONFIG.Hop.PostBlacklist and jobId and httpPost) then return end -- jobId là UUID (không có ký tự cần escape) → nối chuỗi, né JSONEncode httpPost(("%s/api/jobs/%d/blacklist"):format(HOBEO, game.PlaceId), '{"job_id":"' .. tostring(jobId) .. '"}') end -- Teleport ra result=GameEnded ("Could not find requested game instance") = server CHẾT -- → gỡ ngay khỏi pool để clone khác không bị đẩy vào server hỏng. function Hop.reportDead(jobId) if not (CONFIG.Hop.ReportDead and jobId and httpPost) then return end httpPost(("%s/api/jobs/%d/dead"):format(HOBEO, game.PlaceId), '{"job_id":"' .. tostring(jobId) .. '"}') print("[wildpet] [DEAD] bao server chet: " .. tostring(jobId)) end function Hop.fetchServers() local src = Hop.fetchHobeojob() if src and #src > 0 then print(("[wildpet] server source: hobeojob (%d)"):format(#src)) return src end local list, cursor = {}, nil for _ = 1, (CONFIG.Hop.MaxPages or 6) do local url = ("https://games.roblox.com/v1/games/%d/servers/Public?limit=100&excludeFullGames=true") :format(game.PlaceId) .. (cursor and ("&cursor=" .. cursor) or "") local raw = httpGet(url) if not raw then break end local ok, data = pcall(function() return HttpService:JSONDecode(raw) end) if not (ok and data and data.data) then task.wait(1.5) break end -- có thể 429 for _, s in ipairs(data.data) do list[#list + 1] = { job = s.id, playing = tonumber(s.playing) or 0 } end cursor = data.nextPageCursor if not cursor then break end end if #list > 0 then print(("[wildpet] server source: Roblox API (%d, hobeojob trong)"):format(#list)) end return list end function Hop.go() if os.clock() < Hop.noSvNextTry then return false, "dang cho" end -- teleport + chờ TeleportInitFailed đếm theo thời gian thực nhưng vòng chờ dùng task.wait(0.25); -- ở fps 10 mọi thứ giật cục, boost cho cả pha hop rồi tự về. if (CONFIG.Perf or {}).BoostOnHop ~= false then Fps.boost(((CONFIG.Hop.MaxRetries or 12) * ((CONFIG.Hop.WaitPerTry or 6) + (CONFIG.Hop.RetryBackoff or 1.5))) + 10) end local servers = Hop.fetchServers() -- API rỗng: ĐỪNG gọi liên tục (nhiều acc cùng đói → spam → sập). Chờ rồi thử lại. if #servers == 0 then Hop.noSvRetries = Hop.noSvRetries + 1 if Hop.noSvRetries >= (CONFIG.Hop.NoServerMax or 3) then warn(("[wildpet] khong co server %d lan -> Roblox random hop"):format(Hop.noSvRetries)) Hop.noSvRetries, Hop.noSvNextTry, Hop.failed = 0, 0, false pcall(TeleportService.Teleport, TeleportService, game.PlaceId, me()) return true end Hop.noSvNextTry = os.clock() + (CONFIG.Hop.NoServerWait or 60) warn(("[wildpet] khong co server (lan %d/%d) -> cho %ds"):format( Hop.noSvRetries, CONFIG.Hop.NoServerMax or 3, CONFIG.Hop.NoServerWait or 60)) return false, "noserver" end Hop.noSvRetries, Hop.noSvNextTry = 0, 0 local cands = {} for _, s in ipairs(servers) do if s.job and s.job ~= game.JobId and not Hop.visited[s.job] then cands[#cands + 1] = s.job end end if #cands == 0 then Hop.visited = {} warn("[wildpet] het job moi - reset visited") return false, "hetjob" end for i = #cands, 2, -1 do -- shuffle → tránh mọi acc dồn 1 server local j = rng:NextInteger(1, i) cands[i], cands[j] = cands[j], cands[i] end local tries = math.min(#cands, CONFIG.Hop.MaxRetries or 12) print(("[wildpet] HOP: %d ung vien, thu toi da %d"):format(#cands, tries)) for i = 1, tries do local jobId = cands[i] Hop.visited[jobId] = true Hop.failed, Hop.failResult = false, nil -- RESERVE trước khi teleport: clone khác đọc pool sẽ không thấy jobId này nữa if CONFIG.Hop.PostBlacklist then pcall(Hop.blacklistJob, jobId) task.wait(0.2) end print(("[wildpet] HOP %d/%d -> %s"):format(i, tries, jobId)) pcall(TeleportService.TeleportToPlaceInstance, TeleportService, game.PlaceId, jobId, me()) local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if Hop.failed then break end task.wait(0.25) end if not Hop.failed then Hop.count = (Hop.count or 0) + 1 return true -- teleport OK → script chết theo end -- GameEnded = server CHẾT (khác full/rate-limit) → gỡ khỏi pool ngay if Hop.failResult == Enum.TeleportResult.GameEnded then pcall(Hop.reportDead, jobId) end task.wait(CONFIG.Hop.RetryBackoff or 1.5) end if CONFIG.Hop.Matchmaking then warn(("[wildpet] %d lan deu fail -> matchmaking Teleport"):format(tries)) Hop.failed = false pcall(TeleportService.Teleport, TeleportService, game.PlaceId, me()) local t0 = os.clock() while os.clock() - t0 < (CONFIG.Hop.WaitPerTry or 6) do if Hop.failed then break end task.wait(0.25) end if not Hop.failed then return true end end return false, "fail het" end -- ── PRESENCE — heartbeat trạng thái acc cho cả fleet ──────────────────────── -- Chính nó nuôi pool /api/jobs và /api/usernames. Không ai post thì backend mãi rỗng -- (đo live 2/8: /api/jobs/ trả servers:[] vì chưa acc nào báo). local _stats = cloneref and cloneref(game:GetService("Stats")) or game:GetService("Stats") local _bootAt = os.clock() local function netPingMs() local ok, ms = pcall(function() local n = _stats and _stats.Network and _stats.Network.ServerStatsItem if n and n["Data Ping"] then return n["Data Ping"]:GetValue() end return 0 end) ms = (ok and tonumber(ms)) or 0 return math.floor(math.clamp(ms, 0, 600000)) end function Hop.postPresence() if not (CONFIG.Presence and CONFIG.Presence.Enabled and httpRequest) then return end local players = math.min(#Players:GetPlayers(), 200) httpPost(HOBEO .. "/api/presence", HttpService:JSONEncode({ account = tostring(me() and me().Name or ""), -- backend đòi ^[A-Za-z0-9_]+$ (≤20) = username placeId = game.PlaceId, jobId = tostring(game.JobId), players = players, ping = netPingMs(), hops = Hop.count or 0, uptime = math.floor(os.clock() - _bootAt), })) end -- skipFirst = true khi lượt POST đầu tiên ĐÃ được gọi thẳng ở bước init (khỏi post trùng). function Hop.startPresence(skipFirst) if not (CONFIG.Presence and CONFIG.Presence.Enabled) then return end task.spawn(function() if skipFirst then task.wait(CONFIG.Presence.Interval or 60) end while true do pcall(Hop.postPresence) -- CHỈ post heartbeat, KHÔNG get fleet task.wait(CONFIG.Presence.Interval or 60) end end) end -- ── FRIEND REGISTRY — nhận diện acc fleet cùng server ─────────────────────── -- CHỈ GET (không post username; presence ở trên mới là thứ populate list này). -- Cache ĐĨA dùng chung mọi tab trên máy → cả fleet chỉ GET ~1 lần/CacheTTL. local Fleet = { set = {}, ok = false, exp = 0, cloneSince = nil } local function registryFile() return ("WN_registry_%d.json"):format(game.PlaceId) end local function lockFile() return ("WN_reglock_%d.txt"):format(game.PlaceId) end -- ★ BẦU LEADER (port sab_scanner2 L1085-1200). Vì sao cần, khi đã có cache đĩa 30s: -- TTL đơn thuần vẫn thủng — N tab CÙNG thấy cache hết hạn ở giây thứ 30 thì CÙNG đi GET. -- Càng nhiều acc càng đúng lúc (thundering herd). Khoá đảm bảo mỗi cửa sổ TTL chỉ 1 acc -- thực sự gọi mạng, số còn lại đọc file. local function hasFiles() return type(isfile) == "function" and type(readfile) == "function" and type(writefile) == "function" end -- trả: owner, at, torn. -- ★ torn=true = "đọc trúng lúc file đang bị ghi" → coi như CÓ khoá (fail-CLOSED). -- Fail-open ở đây = hàng loạt acc tưởng khoá trống → GET trùng, đúng thứ đang muốn tránh. local function readLock() if not hasFiles() then return nil end local okE, exists = pcall(isfile, lockFile()) if not okE then return nil, nil, true end if not exists then return nil end local okR, raw = pcall(readfile, lockFile()) if not okR or type(raw) ~= "string" then return nil, nil, true end local owner, at = raw:match("^%s*(%d+)|(%d+)%s*$") owner, at = tonumber(owner), tonumber(at) if not owner or not at then return nil, nil, true end -- ghi dở if owner == 0 then return nil end -- "0|0" = trống THẬT if at < 1600000000 or at > os.time() + 600 then return nil, nil, true end return owner, at end local function writeLock(owner, at) if not hasFiles() then return end pcall(writefile, lockFile(), tostring(owner) .. "|" .. tostring(at)) end local function releaseLock() if not hasFiles() then return end -- ★ COMPARE-AND-SET: khoá đã sang acc khác thì TUYỆT ĐỐI không ghi "0|0" đè, -- không thì đá văng leader hợp lệ → acc thứ 3 nhảy vào GET → dây chuyền GET trùng. local self = me() if not self then return end local myId = self.UserId local owner = readLock() if owner and owner ~= myId then return end writeLock(0, 0) end -- true = mình là LEADER (được phép GET). false = có acc khác đang GET → làm follower. local function tryLead(lockTTL, settle) if not hasFiles() then return true end -- không share được → tự GET local self = me() if not self then return false end -- chưa có player → chưa bầu bán gì local myId = self.UserId if myId == 0 then return true end local owner, at, torn = readLock() if torn then return false end if owner and owner ~= myId and (os.time() - at) < lockTTL then return false end -- ★ JITTER TRƯỚC KHI GHI (không phải sau). Jitter sau khi ghi chỉ rải lúc ĐỌC, -- cho ra thứ tự A-ghi/A-đọc/B-ghi/B-đọc → 2 leader cùng lúc. task.wait(math.random() * 1.5) local owner2, at2, torn2 = readLock() if torn2 then return false end if owner2 and owner2 ~= myId and (os.time() - at2) < lockTTL then return false end writeLock(myId, os.time()) task.wait(settle) -- settle ĐỒNG NHẤT (mọi acc bằng nhau) if readLock() ~= myId then return false end -- acc khác ghi đè sau mình → nhường task.wait(settle * 0.5) -- vòng 2: bắt lệnh ghi rơi giữa vòng 1 if readLock() ~= myId then return false end return true end -- ★ CACHE 2 TẦNG + KHOÁ. Mục tiêu: cả máy (mọi tab) chỉ GET ~1 lần mỗi CacheTTL. -- Bản trước có lỗ: vòng RefreshInterval gọi refresh(TRUE) → bỏ qua cache đĩa → N tab -- chạy song song = N lần GET, đúng thứ cần tránh. Giờ KHÔNG còn tham số force nào -- vượt được cache đĩa; hết hạn thì mới có 1 tab thực sự đi mạng. function Fleet.refresh() local fr = CONFIG.FriendRegistry if not (fr and fr.Enabled) then return end local now = os.time() -- 0) in-memory còn hạn → thôi if Fleet.ok and now < Fleet.exp then return end -- 0b) đang có lượt GET chạy trong CHÍNH tab này → đợi nó, đừng bắn thêm if Fleet.fetching then for _ = 1, 100 do task.wait(0.1) if not Fleet.fetching then return end end return end -- 1) cache ĐĨA còn hạn → nạp, KHÔNG gọi mạng. Đây là chỗ chặn giữa các TAB. if isfile and readfile and isfile(registryFile()) then local ok, t = pcall(function() return HttpService:JSONDecode(readfile(registryFile())) end) if ok and type(t) == "table" and type(t.exp) == "number" and now < t.exp and type(t.names) == "table" then Fleet.set, Fleet.ok, Fleet.exp = {}, true, t.exp local n = 0 for _, nm in ipairs(t.names) do Fleet.set[tostring(nm):lower()] = true ; n = n + 1 end print(("[wildpet] [REGISTRY] dung CACHE DIA (%d user, con %ds) — bo GET"):format(n, t.exp - now)) return end end -- 2) BẦU LEADER: chỉ 1 acc trong cả máy được đi mạng mỗi cửa sổ TTL. if not tryLead(fr.LockTTL or 60, fr.LockSettle or 1) then -- FOLLOWER: không GET. Đợi leader ghi xong rồi đọc file. for _ = 1, math.ceil((fr.FollowerWait or 10) / 0.5) do task.wait(0.5) if isfile and readfile and isfile(registryFile()) then local ok, t = pcall(function() return HttpService:JSONDecode(readfile(registryFile())) end) if ok and type(t) == "table" and type(t.exp) == "number" and os.time() < t.exp and type(t.names) == "table" then Fleet.set, Fleet.ok, Fleet.exp = {}, true, t.exp local c = 0 for _, nm in ipairs(t.names) do Fleet.set[tostring(nm):lower()] = true ; c = c + 1 end print(("[wildpet] [REGISTRY] FOLLOWER: doc cache leader vua ghi (%d user)"):format(c)) return end end end -- leader chậm/chết → lùi lịch, vòng sau thử giành khoá Fleet.exp = os.time() + (fr.FailBackoff or 30) print("[wildpet] [REGISTRY] FOLLOWER: leader chua ghi kip -> thu lai sau") return end Fleet.fetching = true -- 3) GET có retry: list fleet đông → host chậm; 1 GET fail = registry rỗng = mù clone local names for i = 1, (fr.GetTries or 4) do local raw = httpGet(("%s/api/usernames/%d"):format(HOBEO, game.PlaceId)) -- ★ CHỐT KÍCH THƯỚC: endpoint này trả CẢ BẢNG người chơi của place, không lọc fleet. -- Đo live 2/8: place SAB = 5.66 MB / 6.9s / 82.732 tên. JSONDecode chừng đó đủ làm -- đứng client vài giây. Quá ngưỡng thì bỏ lượt này còn hơn treo game. if raw and #raw > (fr.MaxBytes or 2000000) then warn(("[wildpet] [REGISTRY] payload %.1f MB > gioi han -> bo qua (dat FriendRegistry.MaxBytes de noi)") :format(#raw / 1048576)) raw = nil end if raw then local ok, data = pcall(function() return HttpService:JSONDecode(raw) end) if ok and type(data) == "table" then names = data.usernames or data.names or data.accounts or (type(data[1]) ~= "nil" and data) if names then break end end end if i < (fr.GetTries or 4) then task.wait(fr.GetRetryGap or 3) end end if not names then Fleet.fetching = false releaseLock() -- ★ nhả ngay: giữ khoá mà không có data = cả fleet mù tới khi hết TTL -- ★ GET fail → lùi lịch thử lại (FailBackoff), đừng để vòng ngoài gọi lại ngay tức thì. Fleet.exp = os.time() + (fr.FailBackoff or 30) -- ★ Đang có data tốt → GIỮ, KHÔNG clobber bằng rỗng (mất data = mù clone). if Fleet.ok then print("[wildpet] [REGISTRY] GET fail -> giu data cu") return end print("[wildpet] [REGISTRY] GET fail, chua co data -> mu clone") return end -- ★ MỖI PHẦN TỬ LÀ OBJECT, KHÔNG PHẢI CHUỖI. Đo live 2/8: -- "usernames":[{"username":"CrimsonybScytheK78","created_at":"2026-07-25 09:46:40"}, ...] -- Bản trước tôi làm tostring(nm) → ra "table: 0x..." → registry toàn rác, không khớp -- tên người chơi nào, mà lại IM LẶNG (vẫn báo "GET OK n user") nên rất khó phát hiện. -- Nhận cả 2 dạng phòng khi backend đổi. Fleet.set = {} local list, n = {}, 0 for _, e in ipairs(names) do local s if type(e) == "string" then s = e elseif type(e) == "table" then s = e.username or e.name or e.account end s = s and tostring(s) or nil if s and s ~= "" then Fleet.set[s:lower()] = true ; list[#list + 1] = s ; n = n + 1 end end if n == 0 and #names > 0 then warn(("[wildpet] [REGISTRY] doc duoc %d phan tu nhung KHONG boc duoc ten nao — schema doi?"):format(#names)) if Fleet.ok then Fleet.fetching = false releaseLock() Fleet.exp = os.time() + (fr.FailBackoff or 30) return -- giữ data cũ còn hơn ghi đè bằng rỗng end end Fleet.ok = true Fleet.exp = os.time() + (fr.CacheTTL or 30) -- ★ GHI FILE TRƯỚC rồi mới nhả khoá. Nhả trước thì có khe cho acc khác giành được khoá -- lúc file chưa kịp có → nó lại GET, thành 2 lần gọi mạng cho cùng một cửa sổ TTL. if writefile then pcall(writefile, registryFile(), HttpService:JSONEncode({ exp = Fleet.exp, names = list })) end Fleet.fetching = false releaseLock() print(("[wildpet] [REGISTRY] LEADER: GET OK (%d user) -> ghi cache dung chung %ds"):format(n, fr.CacheTTL or 30)) end -- Có clone fleet chung server không + RANK của mình theo UserId. -- rank = số clone có UserId NHỎ HƠN mình. UserId nhỏ nhất = rank 0 = grace ngắn nhất = hop TRƯỚC. -- Acc hop trước rời đi → acc còn lại thấy clone biến mất sẽ RESET đồng hồ → đúng 1 acc rời mỗi đợt. function Fleet.standoff() local self = me() if not self then return false, 0, nil, 0 end local myId = self.UserId local cloneName, count, rank = nil, 0, 0 for _, p in ipairs(Players:GetPlayers()) do if p.UserId ~= myId and (Fleet.set[p.Name:lower()] or Fleet.set[tostring(p.DisplayName or ""):lower()]) then count = count + 1 cloneName = cloneName or p.Name if p.UserId < myId then rank = rank + 1 end end end return count > 0, rank, cloneName, count end -- (Fleet.shouldHop ĐÃ BỎ — nó là bộ đếm grace/rank cho mô hình canh-định-kỳ. -- Hop giờ one-shot lúc join nên chỉ cần Fleet.standoff() trả "có ai chưa".) -- tự chạy lại sau khi hop (bỏ trống nếu đã có autoexec re-inject) do local q = (syn and syn.queue_on_teleport) or (fluxus and fluxus.queue_on_teleport) or (getgenv and getgenv().queue_on_teleport) or queue_on_teleport if q and CONFIG.Hop.ScriptUrl and CONFIG.Hop.ScriptUrl ~= "" then pcall(q, ([[loadstring(game:HttpGet("%s"))()]]):format(CONFIG.Hop.ScriptUrl)) end end -- ── chạy ──────────────────────────────────────────────────────────────────── function Scan.start() if Scan.running then print("[wildpet] dang chay roi") return Scan end Scan.running = true -- Low.apply CÓ YIELD (tầng nặng duyệt cả workspace, nhả frame mỗi 400 object) → spawn riêng, -- không để nó chặn travel/scan. Tầng nhẹ thì xong ngay. task.spawn(function() pcall(Low.apply) end) -- Nạp module Worlds SỚM và VÔ ĐIỀU KIỆN: kể cả khi tắt auto-travel vẫn phải biết mình -- đang đứng world nào, nếu không rebuildTargets() sẽ rơi về Targets mặc định (list của Main) -- trong khi thân đang ở Fall Harvest → săn nhầm bộ pet. pcall(World.load, (CONFIG.World or {}).WaitReady) -- ★ QUA MAP TRƯỚC KHI LÀM GÌ KHÁC. -- Đi được → script chết theo teleport, autoexec chạy lại ở world mới (lúc đó nhánh -- "dang o ... roi" khớp nên không đi vòng lại). Không đi được → quét tạm tại chỗ. if (CONFIG.World or {}).Enabled ~= false then local sweeper if (CONFIG.World or {}).ScanWhileWaiting ~= false then -- travel mất vài giây; vẫn quét trong lúc chờ cho khỏi phí (dừng ngay khi ensure xong) sweeper = task.spawn(function() while Scan.running do pcall(Scan.sweep, false) task.wait(CONFIG.ScanSeconds or 3) end end) end pcall(World.ensure) if sweeper then pcall(task.cancel, sweeper) end end -- world đã chốt → chốt luôn danh sách săn của world đó local wid, wlist = rebuildTargets() print(("[wildpet] world = %s (%s) · place %d · san %d loai"):format( wid == "" and "?" or wid, World.displayName(wid), game.PlaceId, #wlist)) if WANT_ALL then -- Targets rỗng: không lọc gì. In luôn danh sách con CÓ THỂ spawn để biết trông chờ gì. local list = {} if PetData then for k, v in pairs(PetData) do if type(v) == "table" then local sc = tonumber(v.SpawnChance) or 0 local acq = PetGating and select(2, pcall(PetGating.IsAcquirable, k)) if sc > 0 and acq ~= false then list[#list + 1] = { k = k, sc = sc, p = tonumber(v.BasePrice) or 0 } end end end table.sort(list, function(a, b) return a.sc < b.sc end) -- hiếm nhất lên đầu end local s = {} for _, e in ipairs(list) do s[#s + 1] = ("%s %s"):format(e.k, pct(e.sc)) end print(("[wildpet] Targets rong -> BAO HET moi con. Co the spawn (%d): %s"):format( #list, table.concat(s, ", "))) else -- báo trước con nào KHÔNG spawn được, để khỏi ngồi chờ vô ích local ok, bad = {}, {} for _, t in ipairs(ACTIVE) do local sp, key = speciesInfo(t) if not sp then bad[#bad + 1] = t .. "(khong co trong PetData)" else local acq = PetGating and select(2, pcall(PetGating.IsAcquirable, key)) local sc = tonumber(sp.SpawnChance) or 0 if sc <= 0 or acq == false then bad[#bad + 1] = ("%s(spawn=%s acquirable=%s)"):format(key, pct(sc), tostring(acq)) else ok[#ok + 1] = ("%s %s $%s"):format(key, pct(sc), money(sp.BasePrice)) end end end print(("[wildpet] san: %s"):format(#ok > 0 and table.concat(ok, " | ") or "(khong con nao hop le)")) if #bad > 0 then warn(("[wildpet] KHONG spawn hoang duoc, bo qua di cho phi: %s"):format(table.concat(bad, ", "))) end end Scan.sweep(CONFIG.ShowOnStart) -- ChildAdded trên WildPetRef = sớm nhất; attribute có thể chậm vài frame nên đợi ngắn local f = Scan.folder() if f then Scan.conn = f.ChildAdded:Connect(function(p) -- BOOST TRƯỚC KHI ĐỌC: vòng chờ attribute bên dưới đếm theo FRAME, ở fps 10 -- thì 12 frame = 1.2s. Boost xong mới đọc → còn ~0.2s như chạy fps 60. if (CONFIG.Perf or {}).BoostOnFind ~= false then Fps.boost() end local info = Scan.read(p) if not info then for _ = 1, 12 do task.wait() if not p.Parent then return end info = Scan.read(p) if info then break end end end if info and isWanted(info.species) then Scan.report(info) end end) end -- ★★ FLEET — CHẠY ĐÚNG 1 LẦN mỗi lần execute. KHÔNG có vòng canh clone. -- Thứ tự BẮT BUỘC (post trước, get sau — đảo lại là acc mình chưa nằm trong list): -- 1) POST /api/presence → tự ghi acc này vào pool = "init" server -- 2) GET /api/usernames → lấy list fleet -- 3) thấy acc fleet khác đang ở đây → mình là người tới SAU → tự giác HOP -- (Hop.go POST /blacklist jobId đích ngay trước khi teleport để clone khác né) -- Acc thứ 1 vào server trống: init xong, list không có ai → Ở LẠI. -- Acc thứ 2 vào sau: init xong, list đã có acc 1 → ĐI. Hội tụ 1 acc/server, không cần loop. Scan.startedAt = os.clock() task.spawn(function() task.wait(CONFIG.Hop.JoinDelay or 5) -- chờ Players:GetPlayers() nạp đủ người pcall(Hop.postPresence) -- (1) init — gọi THẲNG, xong mới đi tiếp Hop.startPresence(true) -- heartbeat nền giữ acc trong pool (bỏ lượt đầu) if not CONFIG.Hop.Enabled then return end if CONFIG.FriendRegistry.Enabled then pcall(Fleet.refresh) -- (2) GET, đúng 1 lượt end local has, _, who, cnt = Fleet.standoff() -- (3) if not has then print("[wildpet] JOIN: khong co acc fleet chung server -> O LAI scan") return end print(("[wildpet] JOIN: gap acc fleet '%s' (x%d) -> minh toi SAU -> HOP"):format( tostring(who), cnt)) pcall(Hop.go) end) -- vòng quét: CHỈ quét, không còn dính gì tới hop task.spawn(function() while Scan.running do task.wait(CONFIG.ScanSeconds) pcall(Scan.sweep, false) end end) return Scan end function Scan.stop() Scan.running = false if Scan.conn then pcall(function() Scan.conn:Disconnect() end) ; Scan.conn = nil end print(("[wildpet] dung. %d lan quet, %d con trung muc tieu"):format(Scan.scans, Scan.hits)) end Scan.CONFIG = CONFIG Scan.start() return Scan