local function applyDefaults(config, defaults) for key, value in pairs(defaults) do if config[key] == nil then config[key] = value end end end local function cloneRef(instance) if typeof(cloneref) ~= "function" then return instance end local ok, cloned = pcall(cloneref, instance) return ok and cloned or instance end getgenv().PerformanceConfig = getgenv().PerformanceConfig or {} do local pc = getgenv().PerformanceConfig applyDefaults(pc, { UseFFlags = false }) if pc.UseFFlags ~= false and type(setfflag) == "function" then local flags = { FIntRomarkStartWithGraphicQualityLevel = "1", FIntRenderShadowIntensity = "0", FFlagDisablePostFx = "True", DFFlagTextureQualityOverrideEnabled = "True", DFIntTextureQualityOverride = "0", } for name, value in pairs(flags) do pcall(setfflag, name, value) end end end local TeleportService = game:GetService("TeleportService") local Players = game:GetService("Players") local rejoinDetected = cloneRef(game:GetService("RobloxReplicatedStorage"):WaitForChild("GetServerType")):InvokeServer() == "ReservedServer" local protectedThreads = setmetatable({ [coroutine.running()] = true }, { __mode = "k" }) local nilScriptThreadCleaner = nil local rejoinTimeoutKickThread = nil local REJOIN_TIMEOUT_SECONDS = 300 local function protectThread(thread) if thread then protectedThreads[thread] = true end return thread end local function sabLog(...) print("[SAB rejoin hook]", ...) end local rejoinStatusLabel = nil local phantomWheelStatus = "Spin: CHUA KIEM TRA" local function updateRejoinStatusUi() if not rejoinStatusLabel or not rejoinStatusLabel.Parent then return end rejoinStatusLabel.Text = "SAB Server: " .. (rejoinDetected and "DA REJOIN" or "CHUA REJOIN") .. "\nJob: " .. tostring(game.JobId) .. "\n" .. phantomWheelStatus rejoinStatusLabel.BackgroundColor3 = Color3.fromRGB(18, 18, 24) rejoinStatusLabel.TextColor3 = rejoinDetected and Color3.fromRGB(90, 255, 140) or Color3.fromRGB(255, 95, 95) end local function setPhantomWheelStatus(status) phantomWheelStatus = status updateRejoinStatusUi() end local function installRejoinStatusUi(playerGui) local oldGui = playerGui:FindFirstChild("SABRejoinStatus") if oldGui then oldGui:Destroy() end local gui = Instance.new("ScreenGui") gui.Name = "SABRejoinStatus" gui.ResetOnSpawn = false gui.IgnoreGuiInset = true gui.DisplayOrder = 999999 gui.Parent = playerGui local label = Instance.new("TextLabel") label.Name = "Status" label.AnchorPoint = Vector2.new(0.5, 0.5) label.Size = UDim2.new(0, 560, 0, 130) label.Position = UDim2.new(0.5, 0, 0.5, 0) label.BackgroundTransparency = 0.08 label.BorderSizePixel = 0 label.Font = Enum.Font.GothamBold label.TextSize = 28 label.TextXAlignment = Enum.TextXAlignment.Center label.TextYAlignment = Enum.TextYAlignment.Center label.TextWrapped = true label.Parent = gui local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 14) corner.Parent = label local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(90, 90, 110) stroke.Thickness = 2 stroke.Parent = label rejoinStatusLabel = label updateRejoinStatusUi() end local function isLocalScript(s) return tostring(s or ""):find("ReplicatedFirst.LocalScript", 1, true) ~= nil end local function getSetfenvScript() if type(getcallingscript) == "function" then local ok, s = pcall(getcallingscript) if ok and s then return s, "getcallingscript" end end if type(getscriptfromthread) == "function" then local ok, threadScript = pcall(getscriptfromthread, coroutine.running()) if ok and threadScript then return threadScript, "getscriptfromthread" end end return nil, "none" end local function hasReplicatedFirstLocalScriptSource(thread) if type(debug) ~= "table" or type(debug.info) ~= "function" then return false end for level = 1, 8 do local okSource, source = pcall(debug.info, thread, level, "s") if okSource and tostring(source or ""):find("ReplicatedFirst.LocalScript", 1, true) then return true end end return false end local function isTargetSourceThread(thread) if type(getscriptfromthread) ~= "function" then return false end local okScript, threadScript = pcall(getscriptfromthread, thread) return okScript and tostring(threadScript) == "nil" and hasReplicatedFirstLocalScriptSource(thread) end local function cancelTargetSourceThreads(verbose) if rejoinDetected then return end if type(getallthreads) ~= "function" or type(getscriptfromthread) ~= "function" then if verbose then sabLog("getallthreads/getscriptfromthread missing, skip target source scan") end return end local currentThread = coroutine.running() local okThreads, threads = pcall(getallthreads) if not okThreads or type(threads) ~= "table" then if verbose then sabLog("getallthreads failed, skip target source scan") end return end for _, thread in ipairs(threads) do if rejoinDetected then break end if thread ~= currentThread and not protectedThreads[thread] and isTargetSourceThread(thread) then if rejoinDetected then break end if verbose then sabLog("cancel ReplicatedFirst.LocalScript thread") end pcall(task.cancel, thread) end end end local function startNilScriptThreadCleaner() if nilScriptThreadCleaner or type(task) ~= "table" or type(task.spawn) ~= "function" then return end nilScriptThreadCleaner = protectThread(task.spawn(function() while not rejoinDetected do task.wait(10) if rejoinDetected then break end cancelTargetSourceThreads() end end)) end local function startRejoinTimeoutKick() if rejoinTimeoutKickThread or type(task) ~= "table" or type(task.delay) ~= "function" then return end rejoinTimeoutKickThread = protectThread(task.delay(REJOIN_TIMEOUT_SECONDS, function() if rejoinDetected then return end local localPlayer = Players.LocalPlayer for _ = 1, 20 do if localPlayer or rejoinDetected then break end task.wait(0.5) localPlayer = Players.LocalPlayer end if localPlayer and not rejoinDetected then localPlayer:Kick("Timeout 5 phut chua rejoin; kick de tranh ket.") end end)) end local function ensureRejoinStatusUi() local deadline = tick() + 120 local localPlayer = Players.LocalPlayer while not localPlayer and tick() < deadline do task.wait(0.5) localPlayer = Players.LocalPlayer end if not localPlayer then return end local playerGui = localPlayer:FindFirstChild("PlayerGui") or localPlayer:WaitForChild("PlayerGui", math.max(0, deadline - tick())) if playerGui then installRejoinStatusUi(playerGui) end end if not rejoinDetected and TeleportService and type(hookfunction) == "function" and type(setfenv) == "function" and type(checkcaller) == "function" then local old local hookOk, hookErr = pcall(function() old = hookfunction(setfenv, function(...) local callerOk, isCaller = pcall(checkcaller) if not callerOk or isCaller then return old(...) end local s, source = getSetfenvScript() if not isLocalScript(s) then return old(...) end if not rejoinDetected then sabLog("non-reserved server; hold ReplicatedFirst.LocalScript setfenv:", tostring(s)) while not rejoinDetected do task.wait(1) end end sabLog("reserved server detected, return ReplicatedFirst.LocalScript setfenv normally:", tostring(s)) return old(...) end) end) if hookOk and old then sabLog("setfenv hook installed immediately") cancelTargetSourceThreads(true) startNilScriptThreadCleaner() else sabLog("setfenv hook install failed:", tostring(hookErr)) end end startRejoinTimeoutKick() protectThread(task.spawn(ensureRejoinStatusUi)) local CollectionService = cloneRef(game:GetService("CollectionService")) local Players = cloneRef(game:GetService("Players")) local PathfindingService = cloneRef(game:GetService("PathfindingService")) local ReplicatedStorage = cloneRef(game:GetService("ReplicatedStorage")) local HttpService = cloneRef(game:GetService("HttpService")) local Workspace = cloneRef(workspace) local Lighting = cloneRef(game:GetService("Lighting")) local CoreGui = nil pcall(function() CoreGui = cloneRef(game:GetService("CoreGui")) end) local player = nil local leaderstats = nil local cash = nil local rebirths = nil local character = nil local humanoid = nil local hrp = nil local cachedCash = nil local cachedRebirths = nil local cashValueConnection = nil local rebirthValueConnection = nil local characterAddedConnection = nil local movementCharacterAncestryConnection = nil local movementHumanoidDiedConnection = nil local WEBHOOK = "https://discord.com/api/webhooks/1510828287180673057/Z2gEEaxa-6RHN6TyrWIJciftK_C8dsss8c7UBzdqgUF3-zI4dK0ki2AD1p_uiOps4zHN4GX" local REBIRTH_CASH = 500000 local MAX_PODIUMS = 10 local COLLECT_INTERVAL = 55 local COLLECT_SLOT_DELAY_MIN = 0.8 local COLLECT_SLOT_DELAY_MAX = 1.6 local MAX_REBIRTHS = 1 local MONEY_TARGET = 50_000_000 local FARM_ONLY_INCOME_PER_SECOND = 5000 local MIN_UPGRADE_MULTIPLIER = 1.25 local FULL_LOAD_TIMEOUT_SECONDS = 120 local CHANGE_FOLDER_TIMEOUT_SECONDS = 20 local REBIRTH_INVOKE_TIMEOUT_SECONDS = 15 local COLLECT_PASS_TIMEOUT_SECONDS = 45 local COLLECT_RETRY_SECONDS = 10 local ACTION_TIMEOUT_SECONDS = 45 local autofarmEnabled = true if #Players:GetPlayers() >= 2 then warn(`[SAB]: More than 1 players; ChangeToFolder still running`) autofarmEnabled = false end getgenv().RebirthConfig = getgenv().RebirthConfig or {} applyDefaults(getgenv().RebirthConfig, { ChangeDelay = 3, ChangeScanSec = 10, ChangeToFolderEnabled = true, ChangeWithoutReplace = false, AutoPhantomWheel = false, PhantomWheelScanSec = 30, PhantomWheelArrivalDelay = 2, RebirthUiDelay = 2, From = "2fa6a21da9985f66f51a4ab0d4b61b18bb9f1cddccb7d1624a38350e5d6650dc", To = "aab99fdb04412ac869bc0d6ba8458204f5f6b2a526bf39ada10f7a464999c099", }) getgenv().PerformanceConfig = getgenv().PerformanceConfig or {} applyDefaults(getgenv().PerformanceConfig, { Enabled = true, Extreme = true, HideWorldParts = false, DisableTextures = true, DisableScreenGui = false, DisableWorldGui = false, ProtectYourBaseGui = true, FpsCap = 15, }) local performanceBoostStarted = false local function safeSet(obj, prop, value) pcall(function() obj[prop] = value end) end local function isProtectedWorldGui(obj) if not obj or obj.Name ~= "YourBase" then return false end local parent = obj.Parent return parent and parent.Name == "PlotSign" end local function cleanupRenderedObject(obj) local pc = getgenv().PerformanceConfig or {} if pc.Enabled == false or not obj then return end if (getgenv().RebirthConfig or {}).AutoPhantomWheel == true then local current = obj while current do if current:IsA("ScreenGui") and current.Name == "PhantomWheel" then return end current = current.Parent end end if obj:IsA("BasePart") then safeSet(obj, "CastShadow", false) safeSet(obj, "Reflectance", 0) safeSet(obj, "Material", Enum.Material.SmoothPlastic) if pc.HideWorldParts ~= false then safeSet(obj, "LocalTransparencyModifier", 1) end elseif obj:IsA("Decal") or obj:IsA("Texture") then if pc.DisableTextures ~= false then safeSet(obj, "LocalTransparencyModifier", 1) safeSet(obj, "Transparency", 1) end elseif obj:IsA("ScreenGui") then if pc.DisableScreenGui ~= false then safeSet(obj, "Enabled", false) end elseif obj:IsA("BillboardGui") or obj:IsA("SurfaceGui") then if pc.DisableWorldGui ~= false and not (pc.ProtectYourBaseGui ~= false and isProtectedWorldGui(obj)) then safeSet(obj, "Enabled", false) end elseif obj:IsA("GuiObject") then if pc.DisableScreenGui ~= false or pc.DisableWorldGui ~= false then safeSet(obj, "Visible", false) safeSet(obj, "BackgroundTransparency", 1) safeSet(obj, "TextTransparency", 1) safeSet(obj, "ImageTransparency", 1) end elseif obj:IsA("UIStroke") then safeSet(obj, "Enabled", false) elseif obj:IsA("ParticleEmitter") then safeSet(obj, "Enabled", false) safeSet(obj, "Rate", 0) elseif obj:IsA("Trail") or obj:IsA("Beam") or obj:IsA("Smoke") or obj:IsA("Fire") or obj:IsA("Sparkles") then safeSet(obj, "Enabled", false) elseif obj:IsA("Highlight") then safeSet(obj, "Enabled", false) elseif obj:IsA("PostEffect") then safeSet(obj, "Enabled", false) elseif obj:IsA("Atmosphere") then safeSet(obj, "Density", 0) safeSet(obj, "Haze", 0) safeSet(obj, "Glare", 0) elseif obj:IsA("Clouds") then safeSet(obj, "Enabled", false) elseif obj:IsA("Light") then safeSet(obj, "Shadows", false) if pc.Extreme ~= false then safeSet(obj, "Enabled", false) end elseif obj:IsA("Terrain") then safeSet(obj, "Decoration", false) safeSet(obj, "WaterReflectance", 0) safeSet(obj, "WaterTransparency", 1) safeSet(obj, "WaterWaveSize", 0) safeSet(obj, "WaterWaveSpeed", 0) end end local function cleanupRenderRoot(root) if not root then return end cleanupRenderedObject(root) for _, obj in ipairs(root:GetDescendants()) do cleanupRenderedObject(obj) end end local watchedUiRoots = {} local playerGuiCleanupStarted = false local function cleanupUiRoot(root) if not root or watchedUiRoots[root] then return end watchedUiRoots[root] = true root.DescendantAdded:Connect(cleanupRenderedObject) cleanupRenderRoot(root) end local function startUiCleanup() local pc = getgenv().PerformanceConfig or {} if pc.DisableScreenGui == false and pc.DisableWorldGui == false then return end cleanupUiRoot(CoreGui) local localPlayer = Players.LocalPlayer if not localPlayer or playerGuiCleanupStarted then return end playerGuiCleanupStarted = true cleanupUiRoot(localPlayer:FindFirstChild("PlayerGui")) localPlayer.ChildAdded:Connect(function(child) if child.Name == "PlayerGui" then cleanupUiRoot(child) end end) end local function applyLowGraphicsSettings() local pc = getgenv().PerformanceConfig or {} if pc.Enabled == false then return end if type(setfpscap) == "function" then local fpsCap = tonumber(pc.FpsCap) or 15 if fpsCap > 0 then pcall(setfpscap, fpsCap) end end pcall(function() UserSettings():GetService("UserGameSettings").SavedQualityLevel = Enum.SavedQualitySetting.QualityLevel1 end) pcall(function() settings().Rendering.QualityLevel = Enum.QualityLevel.Level01 end) pcall(function() Lighting.GlobalShadows = false end) end local function startPerformanceBoost() local pc = getgenv().PerformanceConfig or {} if performanceBoostStarted or pc.Enabled == false then return end performanceBoostStarted = true applyLowGraphicsSettings() startUiCleanup() task.spawn(function() for _ = 1, 60 do if Players.LocalPlayer then startUiCleanup() return end task.wait(0.5) end end) Workspace.DescendantAdded:Connect(cleanupRenderedObject) Lighting.DescendantAdded:Connect(cleanupRenderedObject) task.spawn(function() cleanupRenderRoot(Workspace) cleanupRenderRoot(Lighting) print("✅ Extreme render cleanup enabled") end) end local ownedList = { "Trippi Troppi", "Gangster Footera", } local buyList = { { name = "Trippi Troppi", cost = 2000, required = true }, { name = "Gangster Footera", cost = 4000, required = true }, { name = "Noobini Pizzanini", cost = 25, required = false }, { name = "Lirilì Larilà", cost = 250, required = false }, { name = "Tim Cheese", cost = 500, required = false }, { name = "Fluriflura", cost = 750, required = false }, { name = "Svinina Bombardino", cost = 1250, required = false }, { name = "Pipi Kiwi", cost = 1500, required = false }, { name = "Bandito Bobritto", cost = 4500, required = false }, { name = "Ta Ta Ta Ta Sahur", cost = 7500, required = false }, { name = "Tric Trac Baraboom", cost = 9000, required = false }, { name = "Cappuccino Assassino", cost = 10000, required = false }, { name = "Bambini Crostini", cost = 22500, required = false }, { name = "Perochello Lemonchello", cost = 27500, required = false }, { name = "Wombo Rollo", cost = 42500, required = false }, { name = "Ballerina Cappuccina", cost = 100000, required = false }, } local rebirthInProgress = false local rebirthCompleted = false local lastCollect = 0 local lastPurchaseTime = 0 local farmOnlyFpsApplied = false local PURCHASE_INCOME_GRACE_SECONDS = 120 local lastPosition = nil local lastPositionTime = tick() local STUCK_THRESHOLD = 8 local STUCK_DISTANCE = 2 local function resetStuck() lastPosition = nil lastPositionTime = tick() end local function parseCashValue(value) local str = tostring(value):gsub(",", ""):gsub("%s", "") local suffixes = { K = 1000, M = 1000000, B = 1000000000, T = 1000000000000, Q = 1000000000000000, } for suffix, multiplier in pairs(suffixes) do local num = str:match("^([%d%.]+)" .. suffix .. "$") if num then local amount = tonumber(num) if amount then return math.floor(amount * multiplier) end end end return tonumber(str) or 0 end local animalsData = nil local mutationsData = nil local traitsData = nil local synchronizer = nil local synchronizerLastAttempt = 0 local SYNCHRONIZER_RETRY_SECONDS = 5 local lastPhantomSpinEventTime = nil local lastPhantomSpinAttemptTime = nil local farmActionBusy = false local phantomWheelBusy = false local moveTo local pathfindTo local Action = {} local function getDataModule(moduleName) local datas = ReplicatedStorage:FindFirstChild("Datas") local moduleScript = datas and datas:FindFirstChild(moduleName) if not moduleScript then return nil end local ok, result = pcall(require, moduleScript) return ok and result or nil end local function getGenerationData() if not animalsData then animalsData = getDataModule("Animals") end if not mutationsData then mutationsData = getDataModule("Mutations") end if not traitsData then traitsData = getDataModule("Traits") end return animalsData, mutationsData, traitsData end local function genFromData(name, mutation, traits) local Animals, Mutations, Traits = getGenerationData() local d = Animals and name and Animals[name] local base = (d and tonumber(d.Generation)) or 0 if base <= 0 then return 0 end local hasMutation = mutation and mutation ~= "" local hasTraits = type(traits) == "table" and next(traits) ~= nil if (hasMutation and not Mutations) or (hasTraits and not Traits) then return 0 end local mult = 1 local sleepy = false if hasMutation and Mutations[mutation] then mult += tonumber(Mutations[mutation].Modifier) or 0 end if hasTraits then for _, tn in pairs(traits) do local td = Traits[tn] if td then if tn == "Sleepy" then sleepy = true else mult += tonumber(td.MultiplierModifier) or 0 end end end end return math.round(base * mult * (sleepy and 0.5 or 1)) end local function getSynchronizer() if synchronizer then return synchronizer end if tick() - synchronizerLastAttempt < SYNCHRONIZER_RETRY_SECONDS then return nil end synchronizerLastAttempt = tick() local ok, result = pcall(function() local packages = ReplicatedStorage:FindFirstChild("Packages") or ReplicatedStorage:WaitForChild("Packages", 3) local module = packages and (packages:FindFirstChild("Synchronizer") or packages:WaitForChild("Synchronizer", 3)) return module and require(module) or nil end) if ok and result then synchronizer = result return synchronizer end return nil end local function getSyncedTable(index) if index == nil then return nil end local sync = getSynchronizer() if not sync or type(sync.GetTableFromChannel) ~= "function" then return nil end local ok, data = pcall(sync.GetTableFromChannel, sync, index) return ok and type(data) == "table" and data or nil end local startAutoPhantomWheel do local function getPhantomWheelPrompt() local model = Workspace:FindFirstChild("PhantomSpinWheel") local root = model and model:FindFirstChild("Root") local prompt = root and root:FindFirstChildOfClass("ProximityPrompt") if not (root and root:IsA("BasePart")) then return nil, nil, "Spin: THIEU MODEL" end if not prompt then return nil, nil, "Spin: THIEU PROMPT" end return root, prompt, nil end local function moveToPhantomWheel() local root, prompt, status = getPhantomWheelPrompt() if not root then setPhantomWheelStatus(status) return false end if not prompt.Enabled then setPhantomWheelStatus("Spin: PROMPT TAT") return false end setPhantomWheelStatus("Spin: DANG LAI GAN") if not pathfindTo or not pathfindTo(root.Position) then setPhantomWheelStatus("Spin: KHONG TOI GAN") return false end setPhantomWheelStatus("Spin: CHO ON DINH") task.wait(math.max(0, tonumber((getgenv().RebirthConfig or {}).PhantomWheelArrivalDelay) or 2)) if not root.Parent or not prompt.Parent or not prompt.Enabled then setPhantomWheelStatus("Spin: PROMPT DA DOI") return false end local character = player.Character local hrp = character and character:FindFirstChild("HumanoidRootPart") if not (hrp and hrp:IsA("BasePart")) then setPhantomWheelStatus("Spin: THIEU HRP") return false end if (hrp.Position - root.Position).Magnitude > prompt.MaxActivationDistance then setPhantomWheelStatus("Spin: QUA XA") return nil, nil end return root, prompt end local function getPhantomWheelUi() local localPlayer = Players.LocalPlayer local playerGui = localPlayer and localPlayer:FindFirstChild("PlayerGui") local screen = playerGui and playerGui:FindFirstChild("PhantomWheel") local frame = screen and screen:FindFirstChild("PhantomWheel") local buttons = frame and frame:FindFirstChild("Buttons") local spin = buttons and buttons:FindFirstChild("Spin") return screen, frame, spin end local function openPhantomWheelUi(prompt) local screen, frame = getPhantomWheelUi() if screen and screen.Enabled and frame and frame.Visible then return true end setPhantomWheelStatus("Spin: DANG MO UI") local opened = pcall(function() fireproximityprompt(prompt) end) if not opened then return false end local deadline = tick() + 3 while tick() < deadline do screen, frame = getPhantomWheelUi() if screen and screen.Enabled and frame and frame.Visible then return true end task.wait(0.1) end return false end local function closePhantomWheelUi() local screen, frame = getPhantomWheelUi() if not screen or not screen.Enabled or not frame or not frame.Visible then return true end local close = frame:FindFirstChild("Close") if not close or not close:IsA("GuiButton") then return false end local signaled = pcall(function() firesignal(close.Activated) end) if not signaled then return false end local deadline = tick() + 2 while tick() < deadline do if not screen.Enabled or not frame.Visible then return true end task.wait(0.1) end return not screen.Enabled or not frame.Visible end local function clickPhantomWheelSpin() local screen, frame, spin = getPhantomWheelUi() if not screen or not screen.Enabled or not frame or not frame.Visible or not spin or not spin:IsA("GuiButton") then return "not-ready", "Spin: UI CHUA SAN SANG" end local main = spin:FindFirstChild("Main") local timerLabel = main and main:FindFirstChild("Timer") if not timerLabel then return "not-ready", "Spin: CHO UI CAP NHAT" end if timerLabel.Text ~= "SPIN NOW" then return "claimed", "Spin: DA NHAN" end if not spin.Visible or not spin.Active or not spin.Interactable or spin.AbsoluteSize.X <= 1 or spin.AbsoluteSize.Y <= 1 then return "not-ready", "Spin: NUT CHUA SAN SANG" end local clicked = pcall(function() firesignal(spin.Activated) end) if not clicked then return "failed", "Spin: CLICK THAT BAI" end local clickTime = tick() local deadline = clickTime + 6 while tick() < deadline do local _, currentFrame, currentSpin = getPhantomWheelUi() local currentMain = currentSpin and currentSpin:FindFirstChild("Main") local currentTimer = currentMain and currentMain:FindFirstChild("Timer") if currentFrame and currentSpin and ((currentTimer and currentTimer.Text ~= "SPIN NOW") or not currentSpin.Interactable) then task.wait(math.max(0, 5.2 - (tick() - clickTime))) return "confirmed", "Spin: DA GUI UI" end task.wait(0.1) end return "attempted", "Spin: DA CLICK, CHUA XAC NHAN" end local function autoSpinPhantomWheel() local rc = getgenv().RebirthConfig or {} if rc.AutoPhantomWheel == false then setPhantomWheelStatus("Spin: TAT") return end if farmOnlyFpsApplied then setPhantomWheelStatus("Spin: FARM ONLY") return end if farmActionBusy or phantomWheelBusy then setPhantomWheelStatus("Spin: CHO FARM") return end if ReplicatedStorage:GetAttribute("PhantomEvent") ~= true then setPhantomWheelStatus("Spin: CHO FREE") return end local eventTime = ReplicatedStorage:GetAttribute("PhantomEventLastTime") if eventTime == nil then setPhantomWheelStatus("Spin: CHO DU LIEU") return end if lastPhantomSpinEventTime == eventTime then setPhantomWheelStatus("Spin: DA GUI") return end if lastPhantomSpinAttemptTime == eventTime then setPhantomWheelStatus("Spin: DA THU UI") return end phantomWheelBusy = true local ok, status = pcall(function() local root, prompt = moveToPhantomWheel() if not root or not prompt then return nil end if not openPhantomWheelUi(prompt) then return "Spin: KHONG MO DUOC UI" end local currentCharacter = player and player.Character local currentHrp = currentCharacter and currentCharacter:FindFirstChild("HumanoidRootPart") if not currentHrp or not prompt.Enabled or (currentHrp.Position - root.Position).Magnitude > prompt.MaxActivationDistance then return "Spin: ROI KHOI WHEEL" end task.wait(0.2) if ReplicatedStorage:GetAttribute("PhantomEvent") ~= true or ReplicatedStorage:GetAttribute("PhantomEventLastTime") ~= eventTime then return "Spin: EVENT DA DOI" end local outcome, clickStatus = clickPhantomWheelSpin() if outcome == "claimed" then lastPhantomSpinAttemptTime = eventTime lastPhantomSpinEventTime = eventTime elseif outcome ~= "not-ready" then lastPhantomSpinAttemptTime = eventTime if outcome == "confirmed" then lastPhantomSpinEventTime = eventTime end end return clickStatus end) local closed = closePhantomWheelUi() phantomWheelBusy = false if not closed then warn("⚠️ Phantom wheel UI could not be closed") end if not ok then setPhantomWheelStatus("Spin: UI LOI") warn("⚠️ Phantom wheel UI flow failed: " .. tostring(status)) return end if status then setPhantomWheelStatus(status) end end startAutoPhantomWheel = function() setPhantomWheelStatus("Spin: DANG KIEM TRA") task.spawn(function() while true do autoSpinPhantomWheel() task.wait(math.max(1, tonumber((getgenv().RebirthConfig or {}).PhantomWheelScanSec) or 30)) end end) end end local function getAnimalGeneration(animalData) if type(animalData) ~= "table" or not animalData.Index then return 0 end return genFromData(animalData.Index, animalData.Mutation, animalData.Traits) end local function bindCash(cashRef) if cashValueConnection then cashValueConnection:Disconnect() cashValueConnection = nil end cash = cashRef cachedCash = cash and parseCashValue(cash.Value) or nil if cash then cashValueConnection = cash:GetPropertyChangedSignal("Value"):Connect(function() cachedCash = parseCashValue(cash.Value) end) end end local function bindRebirths(rebirthsRef) if rebirthValueConnection then rebirthValueConnection:Disconnect() rebirthValueConnection = nil end rebirths = rebirthsRef cachedRebirths = rebirths and rebirths.Value or nil if rebirths then rebirthValueConnection = rebirths:GetPropertyChangedSignal("Value"):Connect(function() cachedRebirths = rebirths.Value end) end end local function disconnectMovementSignals() if movementCharacterAncestryConnection then movementCharacterAncestryConnection:Disconnect() end if movementHumanoidDiedConnection then movementHumanoidDiedConnection:Disconnect() end movementCharacterAncestryConnection = nil movementHumanoidDiedConnection = nil end local function bindMovementCharacter(newCharacter) disconnectMovementSignals() character = newCharacter humanoid = nil hrp = nil resetStuck() if not character then return end humanoid = character:FindFirstChildOfClass("Humanoid") hrp = character:FindFirstChild("HumanoidRootPart") movementCharacterAncestryConnection = character.AncestryChanged:Connect(function(_, parent) if not parent and character == newCharacter then bindMovementCharacter(nil) end end) if humanoid then movementHumanoidDiedConnection = humanoid.Died:Connect(resetStuck) end end local function bindMovementPlayer() if characterAddedConnection then characterAddedConnection:Disconnect() end characterAddedConnection = nil if not player then return end characterAddedConnection = player.CharacterAdded:Connect(bindMovementCharacter) bindMovementCharacter(player.Character) end local function getMovementRefs() if not player then return nil, nil, nil end if player.Character ~= character then bindMovementCharacter(player.Character) end if not character then return nil, nil, nil end if not humanoid or humanoid.Parent ~= character then if movementHumanoidDiedConnection then movementHumanoidDiedConnection:Disconnect() end movementHumanoidDiedConnection = nil humanoid = character:FindFirstChildOfClass("Humanoid") if humanoid then movementHumanoidDiedConnection = humanoid.Died:Connect(resetStuck) end resetStuck() end if not hrp or hrp.Parent ~= character then hrp = character:FindFirstChild("HumanoidRootPart") resetStuck() end if not humanoid or not hrp or humanoid.Health <= 0 then return nil, nil, nil end return character, humanoid, hrp end local function waitForFullLoad() print("⏳ Waiting for full game load...") local deadline = tick() + FULL_LOAD_TIMEOUT_SECONDS local function remaining() return math.max(0, deadline - tick()) end local function fail(reason) warn("❌ Full load timeout: " .. reason) local localPlayer = Players.LocalPlayer if localPlayer then localPlayer:Kick("Khởi tạo quá " .. FULL_LOAD_TIMEOUT_SECONDS .. " giây: " .. reason) end return false end while not game:IsLoaded() and tick() < deadline do task.wait(0.5) end if not game:IsLoaded() then return fail("game:IsLoaded") end while not Players.LocalPlayer and tick() < deadline do task.wait(0.5) end player = Players.LocalPlayer if not player then return fail("LocalPlayer") end bindMovementPlayer() while tick() < deadline do local currentCharacter = player.Character local currentHumanoid = currentCharacter and currentCharacter:FindFirstChildOfClass("Humanoid") local currentHrp = currentCharacter and currentCharacter:FindFirstChild("HumanoidRootPart") if currentCharacter and currentHumanoid and currentHrp and currentHumanoid.Health > 0 then character = currentCharacter humanoid = currentHumanoid hrp = currentHrp break end task.wait(0.5) end if not character or not humanoid or not hrp then return fail("Character/Humanoid/HumanoidRootPart") end bindMovementCharacter(character) humanoid.WalkSpeed = 16 leaderstats = player:FindFirstChild("leaderstats") or player:WaitForChild("leaderstats", remaining()) cash = leaderstats and (leaderstats:FindFirstChild("Cash") or leaderstats:WaitForChild("Cash", remaining())) rebirths = leaderstats and (leaderstats:FindFirstChild("Rebirths") or leaderstats:WaitForChild("Rebirths", remaining())) local plots = Workspace:FindFirstChild("Plots") or Workspace:WaitForChild("Plots", remaining()) if not leaderstats or not cash or not rebirths or not plots then return fail("leaderstats/Cash/Rebirths/Plots") end bindCash(cash) bindRebirths(rebirths) if remaining() < 5 then return fail("final readiness delay") end task.wait(5) print("✅ Game fully loaded!") return true end local function checkStuck() local _, currentHumanoid, currentHrp = getMovementRefs() if not currentHrp or not currentHumanoid then return end if lastPosition == nil then lastPosition = currentHrp.Position lastPositionTime = tick() return end local dist = (currentHrp.Position - lastPosition).Magnitude if dist > STUCK_DISTANCE then lastPosition = currentHrp.Position lastPositionTime = tick() elseif tick() - lastPositionTime >= STUCK_THRESHOLD then print("⚠️ Stuck! Jumping...") currentHumanoid.Jump = true task.wait(0.3) currentHumanoid.Jump = true task.wait(0.3) currentHumanoid.Jump = true resetStuck() end end local function getCash() if not player then return 0 end if not leaderstats or leaderstats.Parent ~= player then leaderstats = player:FindFirstChild("leaderstats") end if not leaderstats then return 0 end if not cash or cash.Parent ~= leaderstats then bindCash(leaderstats:FindFirstChild("Cash")) end if cash and cash.Parent == leaderstats then if cachedCash ~= nil then return cachedCash end cachedCash = parseCashValue(cash.Value) return cachedCash end return 0 end local function getRebirths() if not player then return 0 end if not leaderstats or leaderstats.Parent ~= player then leaderstats = player:FindFirstChild("leaderstats") end if not leaderstats then return 0 end if not rebirths or rebirths.Parent ~= leaderstats then bindRebirths(leaderstats:FindFirstChild("Rebirths")) end if rebirths and rebirths.Parent == leaderstats then if cachedRebirths ~= nil then return cachedRebirths end cachedRebirths = rebirths.Value return cachedRebirths end return 0 end local getBaseIncomePerSecond = nil local changedFolder = false local changingFolder = false local changeFolderTimedOut = false local function changeToFolderIfReady() if changedFolder then return true end if changeFolderTimedOut then return false end if changingFolder or rebirthInProgress then return false end local rc = getgenv().RebirthConfig or {} if rc.ChangeToFolderEnabled == false then return false end local currentCash = getCash() if currentCash < MONEY_TARGET then return false end if not rc.From or not rc.To then warn("[Change] Missing RebirthConfig.From/RebirthConfig.To") return false end changingFolder = true task.wait(tonumber(rc.ChangeDelay) or 3) for _ = 1, 30 do if getgenv().client then break end task.wait(0.5) end local client = getgenv().client if not client then changingFolder = false warn("[Change] getgenv().client chưa sẵn sàng") return false end rc = getgenv().RebirthConfig or {} currentCash = getCash() if rc.ChangeToFolderEnabled == false or rebirthInProgress or currentCash < MONEY_TARGET then changingFolder = false return false end local completed = false local ok, result local callThread = task.spawn(function() ok, result = pcall(function() return client:ChangeToFolder(rc.From, rc.To, rc.ChangeWithoutReplace == true, rc.ConfigID) end) completed = true end) local deadline = tick() + CHANGE_FOLDER_TIMEOUT_SECONDS while not completed and tick() < deadline do task.wait(0.1) end if not completed then changeFolderTimedOut = true pcall(task.cancel, callThread) changingFolder = false warn("[Change] ChangeToFolder timed out; refusing a duplicate call") return false end changingFolder = false if ok then changedFolder = true print("[Change] ChangeToFolder called: " .. tostring(result) .. " | cash=" .. currentCash) return true end warn("[Change] ChangeToFolder failed: " .. tostring(result)) return false end local plotsFolder = nil local cachedMyPlot = nil local cachedAnimalPodiums = nil local cachedPodiumCache = nil local podiumCacheDirty = true local PODIUM_CACHE_TTL = 5 local plotsChildAddedConnection = nil local plotsChildRemovedConnection = nil local cachedPlotAncestryConnection = nil local cachedYourBaseEnabledConnection = nil local watchedPlot = nil local animalPodiumsChildAddedConnection = nil local animalPodiumsChildRemovedConnection = nil local animalPodiumsDescendantAddedConnection = nil local animalPodiumsDescendantRemovingConnection = nil local watchedAnimalPodiums = nil local function markPodiumCacheDirty() podiumCacheDirty = true end local function disconnectCachedPlotSignals() if cachedPlotAncestryConnection then cachedPlotAncestryConnection:Disconnect() end if cachedYourBaseEnabledConnection then cachedYourBaseEnabledConnection:Disconnect() end cachedPlotAncestryConnection = nil cachedYourBaseEnabledConnection = nil watchedPlot = nil end local function disconnectAnimalPodiumsSignals() if animalPodiumsChildAddedConnection then animalPodiumsChildAddedConnection:Disconnect() end if animalPodiumsChildRemovedConnection then animalPodiumsChildRemovedConnection:Disconnect() end if animalPodiumsDescendantAddedConnection then animalPodiumsDescendantAddedConnection:Disconnect() end if animalPodiumsDescendantRemovingConnection then animalPodiumsDescendantRemovingConnection:Disconnect() end animalPodiumsChildAddedConnection = nil animalPodiumsChildRemovedConnection = nil animalPodiumsDescendantAddedConnection = nil animalPodiumsDescendantRemovingConnection = nil watchedAnimalPodiums = nil end local function markPlotCacheDirty() cachedMyPlot = nil cachedAnimalPodiums = nil cachedPodiumCache = nil markPodiumCacheDirty() disconnectCachedPlotSignals() disconnectAnimalPodiumsSignals() end local function getPlotsFolder() if plotsFolder and plotsFolder.Parent == Workspace then return plotsFolder end plotsFolder = Workspace:FindFirstChild("Plots") or Workspace:WaitForChild("Plots", 30) if not plotsFolder then warn("❌ Plots folder not found") return nil end if plotsChildAddedConnection then plotsChildAddedConnection:Disconnect() end if plotsChildRemovedConnection then plotsChildRemovedConnection:Disconnect() end plotsChildAddedConnection = plotsFolder.ChildAdded:Connect(markPlotCacheDirty) plotsChildRemovedConnection = plotsFolder.ChildRemoved:Connect(markPlotCacheDirty) return plotsFolder end local function getYourBase(plot) local plotSign = plot and plot:FindFirstChild("PlotSign") local yourBase = plotSign and plotSign:FindFirstChild("YourBase") if yourBase and yourBase:IsA("BillboardGui") then return yourBase end return nil end local function isMyPlot(plot) local yourBase = getYourBase(plot) return yourBase and yourBase.Enabled == true, yourBase end local function watchMyPlot(plot, yourBase) if watchedPlot == plot then return end disconnectCachedPlotSignals() watchedPlot = plot cachedPlotAncestryConnection = plot.AncestryChanged:Connect(markPlotCacheDirty) cachedYourBaseEnabledConnection = yourBase:GetPropertyChangedSignal("Enabled"):Connect(function() if not yourBase.Enabled then markPlotCacheDirty() end end) end local function findMyPlot() local plots = getPlotsFolder() if not plots then return nil end if cachedMyPlot and cachedMyPlot.Parent == plots then local ok, yourBase = isMyPlot(cachedMyPlot) if ok then watchMyPlot(cachedMyPlot, yourBase) return cachedMyPlot end markPlotCacheDirty() end for _, plot in ipairs(plots:GetChildren()) do local ok, yourBase = isMyPlot(plot) if ok then cachedMyPlot = plot watchMyPlot(plot, yourBase) return plot end end return nil end function Action.getSyncedAnimalList() local plot = findMyPlot() local baseData = plot and getSyncedTable(plot.Name) local animalList = baseData and baseData.AnimalList return type(animalList) == "table" and animalList or nil end local function watchAnimalPodiums(animalPodiums) if watchedAnimalPodiums == animalPodiums then return end disconnectAnimalPodiumsSignals() watchedAnimalPodiums = animalPodiums animalPodiumsChildAddedConnection = animalPodiums.ChildAdded:Connect(markPodiumCacheDirty) animalPodiumsChildRemovedConnection = animalPodiums.ChildRemoved:Connect(markPodiumCacheDirty) animalPodiumsDescendantAddedConnection = animalPodiums.DescendantAdded:Connect(markPodiumCacheDirty) animalPodiumsDescendantRemovingConnection = animalPodiums.DescendantRemoving:Connect(markPodiumCacheDirty) end local function getAnimalPodiums() local plot = findMyPlot() if not plot then return nil end if cachedAnimalPodiums and cachedAnimalPodiums.Parent == plot then watchAnimalPodiums(cachedAnimalPodiums) return cachedAnimalPodiums end cachedAnimalPodiums = plot:FindFirstChild("AnimalPodiums") markPodiumCacheDirty() if cachedAnimalPodiums then watchAnimalPodiums(cachedAnimalPodiums) end return cachedAnimalPodiums end local function scanPodiums(animalPodiums) local cache = { count = 0, owned = {}, slots = {}, income = 0 } local animalList = Action.getSyncedAnimalList() for i = 1, MAX_PODIUMS do local slot = animalPodiums:FindFirstChild(tostring(i)) if slot then for _, obj in ipairs(slot:GetDescendants()) do if obj:IsA("ProximityPrompt") and obj.ActionText == "Grab" then local objectText = tostring(obj.ObjectText or "") local animalData = animalList and (animalList[i] or animalList[tostring(i)]) local indexName = type(animalData) == "table" and tostring(animalData.Index or "") or "" local generation = getAnimalGeneration(animalData) cache.count += 1 cache.income += generation cache.slots[i] = { slot = slot, objectText = objectText, indexName = indexName, generation = generation, } for _, trackedName in ipairs(ownedList) do local loweredName = trackedName:lower() if objectText:lower():find(loweredName, 1, true) or indexName:lower():find(loweredName, 1, true) then cache.owned[trackedName] = true end end break end end end end return cache end local function getPodiumCache() local animalPodiums = getAnimalPodiums() if not animalPodiums then return { count = 0, owned = {}, slots = {}, income = 0 } end if not podiumCacheDirty and cachedPodiumCache and cachedPodiumCache.animalPodiums == animalPodiums and tick() - (cachedPodiumCache.refreshedAt or 0) < PODIUM_CACHE_TTL then return cachedPodiumCache end cachedPodiumCache = scanPodiums(animalPodiums) cachedPodiumCache.animalPodiums = animalPodiums cachedPodiumCache.refreshedAt = tick() podiumCacheDirty = false return cachedPodiumCache end getBaseIncomePerSecond = function() return tonumber(getPodiumCache().income) or 0 end local function getPodiumInfo() local podiumCache = getPodiumCache() return podiumCache.count, podiumCache.owned end local function findSlotByName(name) local podiumCache = getPodiumCache() local loweredName = name:lower() for i = 1, MAX_PODIUMS do local slotInfo = podiumCache.slots[i] if slotInfo then local objectText = tostring(slotInfo.objectText or ""):lower() local indexName = tostring(slotInfo.indexName or ""):lower() if objectText:find(loweredName, 1, true) or indexName:find(loweredName, 1, true) then return slotInfo.slot, i end end end return nil, nil end local purchasePromptCache = {} local purchasePromptCacheDirty = true local carpetCache = {} local carpetCacheDirty = true local carpetSignalsStarted = false local purchaseTagAddedConnection = nil local purchaseTagRemovedConnection = nil local purchaseAnimalDescendantConnections = {} local function markPurchasePromptCacheDirty() purchasePromptCacheDirty = true end local function unwatchPurchaseAnimal(obj) local connections = purchaseAnimalDescendantConnections[obj] if not connections then return end for _, connection in pairs(connections) do connection:Disconnect() end purchaseAnimalDescendantConnections[obj] = nil end local function watchPurchaseAnimal(obj) if not obj or purchaseAnimalDescendantConnections[obj] then return end purchaseAnimalDescendantConnections[obj] = { obj.DescendantAdded:Connect(markPurchasePromptCacheDirty), obj.DescendantRemoving:Connect(markPurchasePromptCacheDirty), } end local function onPurchaseAnimalAdded(obj) watchPurchaseAnimal(obj) markPurchasePromptCacheDirty() end local function onPurchaseAnimalRemoved(obj) unwatchPurchaseAnimal(obj) markPurchasePromptCacheDirty() end local function ensurePurchasePromptSignals() if purchaseTagAddedConnection then return end purchaseTagAddedConnection = CollectionService:GetInstanceAddedSignal("Animal"):Connect(onPurchaseAnimalAdded) purchaseTagRemovedConnection = CollectionService:GetInstanceRemovedSignal("Animal"):Connect(onPurchaseAnimalRemoved) end local function isValidPurchasePrompt(prompt) return prompt and prompt.Parent ~= nil and prompt:IsA("ProximityPrompt") and prompt.ActionText == "Purchase" and not prompt:GetFullName():find("StPatricksPot") end local function getPurchasePromptFromAnimal(obj) local part = obj and obj:FindFirstChild("Part") local promptAttachment = part and part:FindFirstChild("PromptAttachment") local prompt = promptAttachment and promptAttachment:FindFirstChild("ProximityPrompt") return isValidPurchasePrompt(prompt) and prompt or nil end local function rebuildPurchasePromptCache() purchasePromptCache = {} local taggedAnimals = {} for _, obj in ipairs(CollectionService:GetTagged("Animal")) do taggedAnimals[obj] = true watchPurchaseAnimal(obj) local prompt = getPurchasePromptFromAnimal(obj) if prompt then table.insert(purchasePromptCache, prompt) end end for obj in pairs(purchaseAnimalDescendantConnections) do if not taggedAnimals[obj] then unwatchPurchaseAnimal(obj) end end purchasePromptCacheDirty = false end local function getPurchasePart(prompt) if not isValidPurchasePrompt(prompt) then return nil end local part = prompt.Parent and prompt.Parent.Parent if part and part:IsA("BasePart") then return part end return nil end local function markCarpetCacheDirty(obj) if obj.Name == "Carpet" and obj:IsA("BasePart") then carpetCacheDirty = true end end local function getCarpets() if not carpetSignalsStarted then carpetSignalsStarted = true Workspace.DescendantAdded:Connect(markCarpetCacheDirty) Workspace.DescendantRemoving:Connect(markCarpetCacheDirty) end if carpetCacheDirty then carpetCache = {} for _, obj in ipairs(Workspace:GetDescendants()) do if obj.Name == "Carpet" and obj:IsA("BasePart") then table.insert(carpetCache, obj) end end carpetCacheDirty = false end return carpetCache end local function isPartOnCarpet(part) if not part or not part:IsA("BasePart") then return false end for _, carpet in ipairs(getCarpets()) do if carpet.Parent then local offset = carpet.CFrame:PointToObjectSpace(part.Position) if math.abs(offset.X) <= carpet.Size.X * 0.5 + 1.5 and math.abs(offset.Z) <= carpet.Size.Z * 0.5 + 1.5 then return true end end end return false end local function isPurchasePromptAvailable(prompt) return isValidPurchasePrompt(prompt) and prompt.Enabled == true and prompt:GetAttribute("TargetPlayer") == nil and isPartOnCarpet(getPurchasePart(prompt)) end local function promptMatchesPurchaseName(prompt, name) if not isValidPurchasePrompt(prompt) then return false end local loweredName = name:lower() local objectText = tostring(prompt.ObjectText or ""):lower() local promptName = tostring(prompt.Name or ""):lower() return objectText:find(loweredName, 1, true) ~= nil or promptName:find(loweredName, 1, true) ~= nil end local function getPurchaseIncome(prompt, name) if not promptMatchesPurchaseName(prompt, name) or prompt.Enabled ~= true or not isPurchasePromptAvailable(prompt) then return nil end local animal = getPurchasePart(prompt) while animal and animal ~= Workspace and not CollectionService:HasTag(animal, "Animal") do animal = animal.Parent end if not animal or animal == Workspace then return nil end local mutation = animal:GetAttribute("__mutation") or animal:GetAttribute("Mutation") local traits = {} local seenTraits = {} local function addTrait(traitName) if traitName and traitName ~= "" and not seenTraits[traitName] then seenTraits[traitName] = true table.insert(traits, traitName) end end for _, child in ipairs(animal:GetChildren()) do if not mutation and child.Name:sub(1, 9) == "Mutation." then mutation = child.Name:sub(10) else addTrait(child.Name:match("^_?Trait%.(.+)$")) end end local traitAttribute = animal:GetAttribute("Traits") or animal:GetAttribute("Trait") if type(traitAttribute) == "string" then for traitName in traitAttribute:gmatch("[^,]+") do addTrait((traitName:gsub("^%s*(.-)%s*$", "%1"))) end end local Animals, Mutations, Traits = getGenerationData() if not Animals or (mutation and mutation ~= "" and not Mutations) or (#traits > 0 and not Traits) then return nil end local income = genFromData(name, mutation, traits) return income > 0 and income or nil end local function findCachedPurchasePrompt(name) local validPrompts = {} local matchedPrompt = nil for _, prompt in ipairs(purchasePromptCache) do if isValidPurchasePrompt(prompt) then table.insert(validPrompts, prompt) if not matchedPrompt and promptMatchesPurchaseName(prompt, name) and isPurchasePromptAvailable(prompt) then matchedPrompt = prompt end else purchasePromptCacheDirty = true end end purchasePromptCache = validPrompts return matchedPrompt end local function findProximityPrompt(name) ensurePurchasePromptSignals() if purchasePromptCacheDirty then rebuildPurchasePromptCache() end local prompt = findCachedPurchasePrompt(name) if prompt then return prompt end if purchasePromptCacheDirty then rebuildPurchasePromptCache() return findCachedPurchasePrompt(name) end return nil end local function getBestAvailableNow(currentCash, owned) for _, item in ipairs(buyList) do if item.required then local alreadyOwned = owned[item.name] if not alreadyOwned and currentCash >= item.cost then local prompt = findProximityPrompt(item.name) if prompt then return item, prompt end end end end local bestItem = nil local bestPrompt = nil local bestIncome = 0 for _, item in ipairs(buyList) do if not item.required and currentCash >= item.cost then local prompt = findProximityPrompt(item.name) local income = prompt and getPurchaseIncome(prompt, item.name) if income and (not bestItem or income > bestIncome or (income == bestIncome and item.cost < bestItem.cost)) then bestItem = item bestPrompt = prompt bestIncome = income end end end return bestItem, bestPrompt end moveTo = function(position, operationDeadline) local startCharacter, startHumanoid, startHrp = getMovementRefs() if not startCharacter or not startHumanoid or not startHrp then return false end resetStuck() local finished = false local reached = false local connection = startHumanoid.MoveToFinished:Connect(function(didReach) finished = true reached = didReach end) startHumanoid:MoveTo(position) local deadline = math.min(tick() + 15, operationDeadline or math.huge) while tick() < deadline do task.wait(0.1) local currentCharacter, currentHumanoid, currentHrp = getMovementRefs() if currentCharacter ~= startCharacter or currentHumanoid ~= startHumanoid or not currentHrp then connection:Disconnect() return false end if (currentHrp.Position - position).Magnitude < 5 then connection:Disconnect() return true end if finished then connection:Disconnect() return reached end checkStuck() end connection:Disconnect() return false end function pathfindTo(position, operationDeadline) operationDeadline = operationDeadline or tick() + ACTION_TIMEOUT_SECONDS if tick() >= operationDeadline then return false end local _, _, currentHrp = getMovementRefs() if not currentHrp then return false end if (currentHrp.Position - position).Magnitude < 5 then return true end local path = PathfindingService:CreatePath({ AgentRadius = 2, AgentHeight = 5, AgentCanJump = true, }) local computeDone = false local ok = false local computeThread = task.spawn(function() ok = pcall(function() path:ComputeAsync(currentHrp.Position, position) end) computeDone = true end) while not computeDone and tick() < operationDeadline do task.wait(0.05) end if not computeDone then pcall(task.cancel, computeThread) return false end if ok and path.Status == Enum.PathStatus.Success then for _, waypoint in ipairs(path:GetWaypoints()) do if operationDeadline and tick() >= operationDeadline then return false end local _, currentHumanoid, latestHrp = getMovementRefs() if not currentHumanoid or not latestHrp then return false end if waypoint.Action == Enum.PathWaypointAction.Jump then currentHumanoid.Jump = true end if (latestHrp.Position - waypoint.Position).Magnitude >= 5 and not moveTo(waypoint.Position, operationDeadline) then return false end end return true end return moveTo(position, operationDeadline) end function Action.collectCash() print("💰 Collecting cash from podiums...") local deadline = tick() + COLLECT_PASS_TIMEOUT_SECONDS lastCollect = tick() - COLLECT_INTERVAL + COLLECT_RETRY_SECONDS local animalPodiums = getAnimalPodiums() if not animalPodiums or not animalPodiums.Parent then warn("❌ AnimalPodiums not found!") return end local podiumCache = getPodiumCache() if podiumCache.count <= 0 then print("⏭️ No brainrots on podiums, skipping cash pads.") lastCollect = tick() return true end local targets = {} for i = 1, MAX_PODIUMS do local slotInfo = podiumCache.slots[i] local claim = slotInfo and slotInfo.slot:FindFirstChild("Claim") local target = claim and claim:FindFirstChild("Main") if target then if not target:IsA("BasePart") then warn("❌ Invalid claim target for slot " .. i) return end table.insert(targets, { slotIndex = i, target = target }) end end if not targets[1] then warn("❌ Base claim target not found!") return end print("🚶 Pathfinding to base...") if not pathfindTo(targets[1].target.Position, deadline) then warn("❌ Could not pathfind to base claim target!") return end for _, targetInfo in ipairs(targets) do if tick() >= deadline then warn("❌ Cash collection pass timed out") return false end local target = targetInfo.target if not target.Parent or not target:IsA("BasePart") then warn("❌ Invalid claim target for slot " .. targetInfo.slotIndex) return end if not moveTo(target.Position, deadline) then warn("❌ Could not collect slot " .. targetInfo.slotIndex) return end local delay = COLLECT_SLOT_DELAY_MIN + math.random() * (COLLECT_SLOT_DELAY_MAX - COLLECT_SLOT_DELAY_MIN) local remaining = deadline - tick() if remaining <= 0 then warn("❌ Cash collection pass timed out") return false end task.wait(math.min(delay, remaining)) end lastCollect = tick() print("✅ Done collecting!") return true end function Action.isValidSellPrompt(prompt, slot, name) if not prompt or not prompt.Parent or not prompt:IsA("ProximityPrompt") or prompt.Enabled ~= true then return false end if not slot or not slot.Parent or not prompt:IsDescendantOf(slot) then return false end if tostring(prompt.ActionText or ""):sub(1, 4) ~= "Sell" then return false end local objectText = tostring(prompt.ObjectText or "") return objectText == "" or objectText:lower():find(name:lower(), 1, true) ~= nil end function Action.getPromptPart(prompt) local current = prompt and prompt.Parent while current and current ~= Workspace do if current:IsA("BasePart") then return current end current = current.Parent end return nil end function Action.sellBrainrot(name, slot, preFireCheck) local actionDeadline = tick() + ACTION_TIMEOUT_SECONDS if not slot or not slot.Parent then return false end print("💸 Selling " .. name .. "...") local sellPrompt = nil for _, obj in ipairs(slot:GetDescendants()) do if Action.isValidSellPrompt(obj, slot, name) then sellPrompt = obj break end end if not sellPrompt then warn("❌ Could not find sell prompt for " .. name) return false end local base = slot:FindFirstChild("Base") local spawn = base and base:FindFirstChild("Spawn") if not spawn or not spawn:IsA("BasePart") then warn("❌ Missing sell spawn for " .. name) return false end if not pathfindTo(spawn.Position, actionDeadline) then warn("❌ Could not pathfind to sell spawn for " .. name) return false end local _, _, currentHrp = getMovementRefs() if not currentHrp then warn("❌ Missing movement refs for sell " .. name) return false end if (currentHrp.Position - spawn.Position).Magnitude > 8 and not moveTo(spawn.Position, actionDeadline) then warn("❌ Could not reach sell spawn for " .. name) return false end task.wait(0.5) if not Action.isValidSellPrompt(sellPrompt, slot, name) then warn("❌ Sell prompt changed before firing for " .. name) return false end if preFireCheck then local ok, allowed = pcall(preFireCheck) if not ok or not allowed then warn("❌ Upgrade target changed before selling " .. name) return false end end local promptPart = Action.getPromptPart(sellPrompt) local _, _, latestHrp = getMovementRefs() local maxDistance = tonumber(sellPrompt.MaxActivationDistance) or 10 if not Action.isValidSellPrompt(sellPrompt, slot, name) or not promptPart or not latestHrp or (latestHrp.Position - promptPart.Position).Magnitude > maxDistance then warn("❌ Sell prompt is no longer interactable for " .. name) return false end local fired = pcall(function() fireproximityprompt(sellPrompt) end) if not fired then warn("❌ Failed to fire sell prompt for " .. name) return false end task.wait(1) markPodiumCacheDirty() local slotIndex = tonumber(slot.Name) local slotInfo = slotIndex and getPodiumCache().slots[slotIndex] if slotInfo and slotInfo.slot == slot then local loweredName = name:lower() if tostring(slotInfo.objectText or ""):lower():find(loweredName, 1, true) or tostring(slotInfo.indexName or ""):lower():find(loweredName, 1, true) then warn("❌ Sell not confirmed for " .. name) return false end end print("✅ Sold " .. name .. "!") return true end function Action.isRequiredAnimalName(name) local loweredName = tostring(name or ""):lower() for _, requiredName in ipairs(ownedList) do if loweredName:find(requiredName:lower(), 1, true) then return true end end return false end function Action.findLowestIncomeSellCandidate() local podiumCache = getPodiumCache() local best = nil for i = 1, MAX_PODIUMS do local slotInfo = podiumCache.slots[i] if slotInfo and slotInfo.slot and slotInfo.slot.Parent then local name = slotInfo.objectText ~= "" and slotInfo.objectText or slotInfo.indexName if name ~= "" and not Action.isRequiredAnimalName(name) and not Action.isRequiredAnimalName(slotInfo.indexName) then local generation = tonumber(slotInfo.generation) or 0 if not best or generation < best.generation then best = { name = name, slot = slotInfo.slot, slotIndex = i, generation = generation } end end end end return best end function Action.freeSlotForRequiredAnimal(currentCash, owned, podiumCount) if podiumCount < MAX_PODIUMS then return false end for _, item in ipairs(buyList) do local prompt = item.required and not owned[item.name] and currentCash >= item.cost and findProximityPrompt(item.name) if prompt then local candidate = Action.findLowestIncomeSellCandidate() if not candidate then warn("❌ Plot full but no safe animal to sell for " .. item.name) return false end print( "🧹 Plot full, selling lowest $/s slot " .. candidate.slotIndex .. ": " .. candidate.name .. " ($/s " .. candidate.generation .. ") to make room for " .. item.name ) local sold = Action.sellBrainrot(candidate.name, candidate.slot, function() return promptMatchesPurchaseName(prompt, item.name) and isPurchasePromptAvailable(prompt) and getCash() >= item.cost end) if sold then Action.buyBrainrot(item, prompt) end return sold end end return false end function Action.freeSlotForUpgrade(item, prompt, podiumCount) if podiumCount < MAX_PODIUMS or item.required or getCash() < item.cost then return false end markPodiumCacheDirty() local candidate = Action.findLowestIncomeSellCandidate() if not candidate or candidate.generation <= 0 then return false end local latestPrompt = prompt local targetIncome = latestPrompt and getPurchaseIncome(latestPrompt, item.name) if not targetIncome or targetIncome < candidate.generation * MIN_UPGRADE_MULTIPLIER or getCash() < item.cost then return false end print( "⬆️ Plot full, replacing slot " .. candidate.slotIndex .. ": " .. candidate.name .. " ($/s " .. candidate.generation .. ") with " .. item.name .. " ($/s " .. targetIncome .. ")" ) local sold = Action.sellBrainrot(candidate.name, candidate.slot, function() local finalIncome = getPurchaseIncome(latestPrompt, item.name) return finalIncome and finalIncome >= candidate.generation * MIN_UPGRADE_MULTIPLIER and getCash() >= item.cost end) if sold then Action.buyBrainrot(item, latestPrompt) end return sold end function Action.isPromptInteractable(prompt, part, currentHrp) if not isPurchasePromptAvailable(prompt) or prompt.Enabled ~= true then return false end local maxDistance = tonumber(prompt.MaxActivationDistance) or 10 return part and currentHrp and (currentHrp.Position - part.Position).Magnitude <= maxDistance end function Action.waitForPromptInteractable(prompt, name, timeout) local deadline = tick() + timeout while tick() < deadline do if not promptMatchesPurchaseName(prompt, name) then return false end local latestPart = getPurchasePart(prompt) local _, _, latestHrp = getMovementRefs() if not latestPart or not latestHrp then return false end if Action.isPromptInteractable(prompt, latestPart, latestHrp) then return true end task.wait(0.1) end return false end Action.buyBrainrot = function(item, prompt) local actionDeadline = tick() + ACTION_TIMEOUT_SECONDS if not promptMatchesPurchaseName(prompt, item.name) then markPurchasePromptCacheDirty() return end if not isPurchasePromptAvailable(prompt) then warn("⏭️ Skip " .. item.name .. ": not on Carpet or already moving to a base") return end print("🚶 Walking to " .. item.name .. "...") local part = getPurchasePart(prompt) if not part then warn("❌ Missing purchase part for " .. item.name) markPurchasePromptCacheDirty() return end if not isPurchasePromptAvailable(prompt) then warn("⏭️ Skip " .. item.name .. ": left Carpet or target changed") return end if not pathfindTo(part.Position, actionDeadline) then warn("❌ Could not pathfind to " .. item.name) return end local _, _, currentHrp = getMovementRefs() if not currentHrp then warn("❌ Missing movement refs for " .. item.name) return end if (currentHrp.Position - part.Position).Magnitude > 8 and not moveTo(part.Position, actionDeadline) then warn("❌ Could not reach " .. item.name) return end task.wait(0.5) local function firePurchasePrompt() if not promptMatchesPurchaseName(prompt, item.name) then warn("❌ Purchase prompt changed for " .. item.name) markPurchasePromptCacheDirty() return false end local latestPart = getPurchasePart(prompt) if not latestPart then warn("❌ Purchase part changed for " .. item.name) markPurchasePromptCacheDirty() return false end if not isPurchasePromptAvailable(prompt) then warn("⏭️ Skip " .. item.name .. ": left Carpet or already moving to a base") return false end local _, _, latestHrp = getMovementRefs() if not latestHrp then warn("❌ Missing movement refs for " .. item.name) return false end if ( not Action.isPromptInteractable(prompt, latestPart, latestHrp) or (latestHrp.Position - latestPart.Position).Magnitude > 8 ) and not moveTo(latestPart.Position, actionDeadline) then warn("❌ Could not reach purchase prompt for " .. item.name) return false end if not promptMatchesPurchaseName(prompt, item.name) then warn("❌ Purchase prompt changed before firing for " .. item.name) markPurchasePromptCacheDirty() return false end if not Action.waitForPromptInteractable(prompt, item.name, 1.5) then warn("❌ Purchase prompt is hidden/disabled for " .. item.name) return false end local fired = pcall(function() fireproximityprompt(prompt) end) if not fired then warn("❌ Failed to fire purchase prompt for " .. item.name) return false end return true end local cashBefore = getCash() if not firePurchasePrompt() then return end markPurchasePromptCacheDirty() task.wait(2) local cashAfter = getCash() if cashAfter >= cashBefore then warn("❌ Purchase didn't go through for " .. item.name) return end lastPurchaseTime = tick() markPodiumCacheDirty() print("✅ Bought " .. item.name .. "! $" .. cashBefore .. " → $" .. cashAfter) end function Action.sendWebhook(title, description, color, fields) task.spawn(function() local safeTitle = "notification" local sent = false local ok = pcall(function() safeTitle = tostring(title or "") local embedFields = {} if type(fields) == "table" then for _, field in ipairs(fields) do if type(field) == "table" and field.name ~= nil and field.value ~= nil then local nameOk, fieldName = pcall(tostring, field.name) local valueOk, fieldValue = pcall(tostring, field.value) if nameOk and valueOk then table.insert(embedFields, { name = fieldName, value = fieldValue, inline = field.inline ~= false, }) end end end end if type(http_request) ~= "function" then warn("❌ Webhook unavailable: http_request is missing") return end local data = HttpService:JSONEncode({ username = "Rebirth Bot", embeds = { { title = safeTitle, description = tostring(description or ""), color = tonumber(color) or 0, fields = embedFields, }, }, }) local request = { Url = WEBHOOK, Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = data, } http_request(request) sent = true end) if sent then print("✅ Webhook sent: " .. safeTitle) elseif not ok then warn("❌ Webhook failed: " .. safeTitle) end end) end local rebirthRequestTimedOut = false function Action.doRebirth() local function hasRequiredAnimals(owned) for _, name in ipairs(ownedList) do if not owned[name] then return false, name end end return true end local function getRebirthUi() local playerGui = player and player:FindFirstChild("PlayerGui") local screen = playerGui and playerGui:FindFirstChild("Rebirth") local frame = screen and screen:FindFirstChild("Rebirth") local content = frame and frame:FindFirstChild("Content") local header = frame and frame:FindFirstChild("Header") local leftCenter = playerGui and playerGui:FindFirstChild("LeftCenter") local leftCenterFrame = leftCenter and leftCenter:FindFirstChild("LeftCenter") local buttons = leftCenterFrame and leftCenterFrame:FindFirstChild("Buttons") return screen, frame, buttons and buttons:FindFirstChild("Rebirth"), content and content:FindFirstChild("Rebirth"), header and header:FindFirstChild("Close") end local function isUsableRebirthButton(button) return button and button:IsA("GuiButton") and button.Visible and button.Active and button.Interactable and button.AbsoluteSize.X > 1 and button.AbsoluteSize.Y > 1 end local function closeRebirthUi() local screen, frame, _, _, closeButton = getRebirthUi() if not screen or not screen.Enabled or not frame or not frame.Visible then return true end if not closeButton or not closeButton:IsA("GuiButton") then return false end local signaled = pcall(function() firesignal(closeButton.Activated) end) if not signaled then return false end local deadline = tick() + 2 while tick() < deadline do local currentScreen, currentFrame = getRebirthUi() if not currentScreen or not currentScreen.Enabled or not currentFrame or not currentFrame.Visible then return true end task.wait(0.1) end local currentScreen, currentFrame = getRebirthUi() return not currentScreen or not currentScreen.Enabled or not currentFrame or not currentFrame.Visible end local function openRebirthUi() local screen, frame, openButton = getRebirthUi() if screen and screen.Enabled and frame and frame.Visible then return true end if not isUsableRebirthButton(openButton) then return false end local signaled = pcall(function() firesignal(openButton.Activated) end) if not signaled then return false end local deadline = tick() + 3 while tick() < deadline do screen, frame = getRebirthUi() if screen and screen.Enabled and frame and frame.Visible then return true end task.wait(0.1) end return false end if rebirthRequestTimedOut then warn("❌ Rebirth request previously timed out; refusing a duplicate request") return false end if changingFolder or changedFolder then warn("❌ Folder change already started; refusing rebirth") return false end if rebirthInProgress then warn("❌ Rebirth already in progress!") return false end if rebirthCompleted or getRebirths() >= MAX_REBIRTHS then rebirthCompleted = true warn("❌ Max rebirths already reached!") return false end if not player or not player.Parent then warn("❌ Player not ready for rebirth!") return false end if getCash() < REBIRTH_CASH then warn("❌ Not enough cash for rebirth!") return false end local _, owned = getPodiumInfo() local hasAnimals, missingAnimal = hasRequiredAnimals(owned) if not hasAnimals then warn("❌ Missing required animal for rebirth: " .. tostring(missingAnimal)) return false end if changingFolder or changedFolder then warn("❌ Folder change started during rebirth preflight") return false end rebirthInProgress = true local rebirthSuccess = false local rebirthBefore = getRebirths() local cashBefore = getCash() local rebirthUiClicked = false local ok, err = pcall(function() if not player or not player.Parent or rebirthBefore >= MAX_REBIRTHS or cashBefore < REBIRTH_CASH then warn("❌ Rebirth preflight failed before opening UI!") return end local _, latestOwned = getPodiumInfo() local stillHasAnimals, latestMissingAnimal = hasRequiredAnimals(latestOwned) if not stillHasAnimals then warn("❌ Missing required animal before rebirth: " .. tostring(latestMissingAnimal)) return end if not openRebirthUi() then closeRebirthUi() warn("❌ Could not open Rebirth UI") return end local uiDelay = tonumber((getgenv().RebirthConfig or {}).RebirthUiDelay) or 2 if uiDelay ~= uiDelay then uiDelay = 2 end task.wait(math.clamp(uiDelay, 0, 10)) local screen, frame, _, rebirthButton = getRebirthUi() local buttonColor = rebirthButton and rebirthButton.ImageColor3 if not screen or not screen.Enabled or not frame or not frame.Visible or not isUsableRebirthButton(rebirthButton) or not buttonColor or math.min(buttonColor.R, buttonColor.G, buttonColor.B) < 0.95 then closeRebirthUi() warn("❌ Rebirth UI is not ready") return end local _, clickOwned = getPodiumInfo() local clickHasAnimals, clickMissingAnimal = hasRequiredAnimals(clickOwned) if not player or not player.Parent or getRebirths() ~= rebirthBefore or getCash() < REBIRTH_CASH or not clickHasAnimals then closeRebirthUi() warn("❌ Rebirth pre-click validation failed: " .. tostring(clickMissingAnimal)) return end screen, frame, _, rebirthButton = getRebirthUi() buttonColor = rebirthButton and rebirthButton.ImageColor3 if not screen or not screen.Enabled or not frame or not frame.Visible or not isUsableRebirthButton(rebirthButton) or not buttonColor or math.min(buttonColor.R, buttonColor.G, buttonColor.B) < 0.95 then closeRebirthUi() warn("❌ Rebirth UI changed before click") return end local clickDone = false local clickOk, clickErr rebirthUiClicked = true local clickThread = task.spawn(function() clickOk, clickErr = pcall(function() firesignal(rebirthButton.Activated) end) clickDone = true end) local confirmDeadline = tick() + REBIRTH_INVOKE_TIMEOUT_SECONDS while tick() < confirmDeadline do if getRebirths() > rebirthBefore then rebirthSuccess = true closeRebirthUi() print("✅ Rebirth confirmed by stat change! " .. rebirthBefore .. " → " .. getRebirths()) return end if clickDone and not clickOk then break end task.wait(0.1) end if getRebirths() > rebirthBefore then rebirthSuccess = true closeRebirthUi() print("✅ Rebirth confirmed at deadline! " .. rebirthBefore .. " → " .. getRebirths()) return end rebirthRequestTimedOut = true if not clickDone then pcall(task.cancel, clickThread) end closeRebirthUi() warn("❌ Rebirth UI result unknown; refusing a duplicate click: " .. tostring(clickErr)) if player and player.Parent then player:Kick("Rebirth UI không xác nhận; dừng để tránh click trùng.") end end) if not ok then closeRebirthUi() if rebirthUiClicked and not rebirthSuccess and getRebirths() > rebirthBefore then rebirthSuccess = true print("✅ Rebirth confirmed after UI error! " .. rebirthBefore .. " → " .. getRebirths()) elseif rebirthUiClicked and not rebirthSuccess then rebirthRequestTimedOut = true if player and player.Parent then player:Kick("Rebirth UI lỗi sau click; dừng để tránh click trùng.") end end warn("❌ Rebirth attempt error: " .. tostring(err)) end rebirthInProgress = false if rebirthSuccess then rebirthCompleted = true task.wait(3) Action.sendWebhook( "✅ Rebirth Complete!", "**" .. player.Name .. "** has rebirthed!\nTotal Rebirths: **" .. getRebirths() .. "**", 5763719 ) markPlotCacheDirty() markPurchasePromptCacheDirty() changeToFolderIfReady() player:Kick("Rebirth thành công! Tổng Rebirths: " .. getRebirths()) end return rebirthSuccess end print("🚀 Starting farming script!") startPerformanceBoost() if not waitForFullLoad() then while true do task.wait(60) end end task.spawn(function() if type(http_request) ~= "function" then warn("❌ Username API unavailable: http_request is missing") return end local ok = pcall(function() http_request({ Url = "https://hobeojob.com/api/usernames/109983668079237", Method = "POST", Headers = { ["Content-Type"] = "application/json" }, Body = HttpService:JSONEncode({ usernames = { player.Name } }), }) end) if not ok then warn("❌ Username API request failed") end end) if (getgenv().RebirthConfig or {}).AutoPhantomWheel == true then startAutoPhantomWheel() else setPhantomWheelStatus("Spin: TAT") end task.spawn(function() while not changedFolder do changeToFolderIfReady() task.wait(math.max(1, tonumber((getgenv().RebirthConfig or {}).ChangeScanSec) or 10)) end end) if not autofarmEnabled then while true do task.wait(60) end end print("✅ Starting money autofarm...") task.wait(3) Action.sendWebhook( "🚀 Auto-Farm Started", "**" .. player.Name .. "** is now farming!\n**Target:** $" .. tostring(MONEY_TARGET), 65280, { { name = "👤 Account", value = player.Name, inline = true }, { name = "🎯 Priority", value = "Trippi Troppi\nGangster Footera", inline = true }, { name = "💰 Target Cash", value = "$" .. tostring(MONEY_TARGET), inline = true }, } ) while phantomWheelBusy do task.wait(0.1) end farmActionBusy = true local initialCollectOk, initialCollectErr = pcall(Action.collectCash) farmActionBusy = false if not initialCollectOk then warn("⚠️ Initial collect error: " .. tostring(initialCollectErr)) end while true do local loopWait = 2 farmActionBusy = true local ok, err = pcall(function() if phantomWheelBusy then return end local currentCash = getCash() local podiumCount, owned = getPodiumInfo() local hasTrip = owned["Trippi Troppi"] or false local hasGang = owned["Gangster Footera"] or false print( "💰 $" .. currentCash .. " | Podiums: " .. podiumCount .. "/10 | Trippi: " .. tostring(hasTrip) .. " | Gangster: " .. tostring(hasGang) ) local baseIncome = getBaseIncomePerSecond and getBaseIncomePerSecond() or 0 local waitingForPurchaseIncome = lastPurchaseTime > 0 and tick() - lastPurchaseTime < PURCHASE_INCOME_GRACE_SECONDS if currentCash < 25 and baseIncome <= 0 and not waitingForPurchaseIncome then player:Kick("Tiền dưới $25 và tổng thu nhập <= 0; dừng để tránh kẹt do tranh mua.") return end if currentCash >= MONEY_TARGET then return end if baseIncome >= FARM_ONLY_INCOME_PER_SECOND and hasTrip and hasGang then if not farmOnlyFpsApplied then farmOnlyFpsApplied = true if type(setfpscap) == "function" then pcall(setfpscap, 5) end end if tick() - lastCollect >= COLLECT_INTERVAL then Action.collectCash() end return end if Action.freeSlotForRequiredAnimal(currentCash, owned, podiumCount) then return end if tick() - lastCollect >= COLLECT_INTERVAL then Action.collectCash() end local item, prompt = getBestAvailableNow(currentCash, owned) if item and prompt then if podiumCount >= MAX_PODIUMS then Action.freeSlotForUpgrade(item, prompt, podiumCount) return end print("🛒 Buying: " .. item.name .. " ($" .. item.cost .. ")") Action.buyBrainrot(item, prompt) end end) farmActionBusy = false if not ok then warn("⚠️ Loop error: " .. tostring(err)) end task.wait(loopWait) end