-- ===================================================================== -- MADE BY deepseek-v4-flash INSIDE PROJECT UAI -- Legends of Speed Ultimate Autofarm v5 -- ===================================================================== -- -- >> UI LIB EDITION (file: los_autofarm_v5_ui.lua) -- Same v5 farm core; the hand-built HUD is replaced by the Project -- UAI UI LIB window from -- https://raw.githubusercontent.com/Project-Ptolemy/ProjectUAI -- los_autofarm_v5.lua itself is untouched. -- -- v5 was written against the LIVE place 3101667897 on 2026-09-21, using the -- decompiled client scripts (Potassium 2.4.9) instead of guesswork. -- -- WHAT v4 GOT WRONG, AND WHAT v5 DOES INSTEAD (all measured in-game) -- --------------------------------------------------------------------- -- * EGG HATCHING WAS 50x TOO SLOW AND SILENTLY DEAD. v4 held E for 0.8s beside -- the crystal and hopped 34 studs out and back, giving ~1 hatch per 7s. The -- real call the game makes is one line inside crystalOpenScript: -- openCrystalRemote:InvokeServer("openCrystal", " Crystal") -- v5 calls it directly. No menu, no E, no movement, no proximity: the client's -- 30-stud check lives in crystalOpenScript.openOneCrystal and the server does -- not repeat it (verified hatching from 400+ studs away). -- MEASURED v5: 4.4-9.4 hatches/s, zero failures. -- * HATCHING DIES AT FULL CAPACITY. openOneCrystal refuses when -- calculatePetCapacity(petsFolder) or calculateTrailCapacity(trailsFolder) -- reaches maxPetCapacity, and the server refuses too (0 gems spent, nil back). -- v4 reported "crystal busy" forever. v5 adds a SELL ENGINE that keeps N free -- slots, so hatching never stalls. -- * NOTHING WAS EVER SOLD OR EQUIPPED. Real shapes, read from the GUI scripts: -- sellPetEvent:FireServer("sellPet", petInstance) -- sellTrailEvent:FireServer("sellTrail", trailInstance) -- equipPetEvent:FireServer("equipPet" / "unequipPet", petInstance) -- petEvolveEvent:FireServer("evolvePet", pet.Name) -- evolveTrailEvent:FireServer("evolveTrail", trailInstance) -- rebirthEvent:FireServer("rebirthRequest") -- rEvents.ultimatesRemote:InvokeServer("upgradeUltimate", "") -- rEvents.checkChestRemote:InvokeServer("") -- Sell value is globalFunctions.calculatePetValue(tier, level, evolved). -- * THE DROP TABLES ARE PUBLIC. ReplicatedStorage.crystalChances lists every -- crystal's pool with chanceValue/nameValue/rarityValue, and cPetShopFolder -- prices every pet and trail. v5 reads both, so it can snipe a named pet -- ("Swift Samurai", Jungle Crystal, 1%) instead of hatching blind. -- Jungle Crystal: Swift Samurai Omega 1%, Golden Viking Unique 20%, -- Speedy Sensei Unique 14%, Maestro Dog Unique 35%, Divine Pegasus Epic 30%. -- Electro Legends Crystal: six Omegas, 2-30% each (no rare ones). -- * ORB FARM, HOOP LOOP, QUESTS were already right in v4 and are unchanged. -- orbEvent:FireServer("collectOrb", orbName, currentMap.Value) is exactly what -- oScript fires; hoopEvent is server->client only, so relocating Workspace.Hoops -- onto the character is still the only lever. -- -- NEW IN v5: sell engine (ranked by real pet/trail prices, never sells equipped), -- auto-equip that frees a slot first, pet sniper with live chance math, auto -- rebirth, auto ultimate upgrades, chest collector, session income stats. -- ===================================================================== local Players = game:GetService("Players") local player = Players.LocalPlayer local RS = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") local UserInputService = game:GetService("UserInputService") local HttpService = game:GetService("HttpService") local rEvents = RS:WaitForChild("rEvents", 30) local function remote(name) local r = rEvents and rEvents:FindFirstChild(name) return r end local orbEvent = remote("orbEvent") local questsEvent = remote("questsEvent") local openCrystalRemote = remote("openCrystalRemote") local sellPetEvent = remote("sellPetEvent") local sellTrailEvent = remote("sellTrailEvent") local equipPetEvent = remote("equipPetEvent") local petEvolveEvent = remote("petEvolveEvent") local evolveTrailEvent = remote("evolveTrailEvent") local rebirthEvent = remote("rebirthEvent") local speedRampEvent = remote("speedRampEvent") local ultimatesRemote = remote("ultimatesRemote") local checkChestRemote = remote("checkChestRemote") local GF = nil pcall(function() GF = require(RS:WaitForChild("globalFunctions")) end) local MONO = { bg = Color3.fromRGB(18, 18, 20), panel = Color3.fromRGB(26, 26, 30), panel2 = Color3.fromRGB(34, 34, 40), border = Color3.fromRGB(48, 48, 56), text = Color3.fromRGB(235, 235, 240), sub = Color3.fromRGB(150, 150, 160), accent = Color3.fromRGB(200, 200, 210), success = Color3.fromRGB(120, 255, 160), warn = Color3.fromRGB(255, 200, 90), danger = Color3.fromRGB(255, 90, 90), rare = Color3.fromRGB(180, 140, 255), } local state = { -- hatch hatcher = false, hatchThread = nil, hatchChoice = "Jungle", hatchRate = 8, hatchMax = 0, gemReserve = 0, autoSell = true, keepTop = 6, keepFree = 3, snipePet = "", hatches = 0, rarity = {}, hits = {}, -- sell / equip autoEquip = false, equipThread = nil, -- other loops orbFarming = false, orbChoice = nil, orbRate = 1200, orbAmount = 250000, orbInfinite = true, orbThread = nil, hoopLoop = false, hoopThread = nil, quest = false, questThread = nil, evolvePets = false, petThread = nil, evolveTrails = false, trailThread = nil, ramps = false, rampThread = nil, autoRebirth = false, rebirthThread = nil, rebirthsDone = 0, rebirthReserve = 3, autoUltimates = false, ultimateThread = nil, ultimatesBought = 0, chests = false, chestThread = nil, chestsOpened = 0, keepNames = {}, minimised = false, session = {}, } -- ============ INSTANCE GUARD ============ local GENV = _G pcall(function() if type(getgenv) == "function" then GENV = getgenv() end end) local INSTANCE_ID = (GENV.__LOS_AUTOFARM_ID or 0) + 1 GENV.__LOS_AUTOFARM_ID = INSTANCE_ID local function isCurrent() return GENV.__LOS_AUTOFARM_ID == INSTANCE_ID end -- replaced with the real thing once the GUI exists (declared here so every loop -- below can report through it) local setStatus = function() end -- toast, replaced once the GUI exists local notify = function() end -- names that must never be sold (filled from the GUI / API) state.keepNames = {} local function spawnLoop(fn) local co = coroutine.create(function() local ok, err = pcall(fn) if not ok then warn("[LOS Autofarm v5] loop error: " .. tostring(err)) end end) local ok, err = coroutine.resume(co) if not ok then warn("[LOS Autofarm v5] loop error: " .. tostring(err)) end return co end -- ============ LIVE GAME DATA ============ local function shorten(n) n = tonumber(n) or 0 local s = { "", "K", "M", "B", "T", "Qa", "Qi" } local i = 1 while n >= 1000 and i < #s do n = n / 1000; i = i + 1 end return i == 1 and tostring(math.floor(n)) or string.format("%.2f%s", n, s[i]) end local function statValue(name) local v = player:FindFirstChild(name) if v and v:IsA("ValueBase") then return v.Value end local ls = player:FindFirstChild("leaderstats") v = ls and ls:FindFirstChild(name) return v and v.Value or 0 end local function shadowNum(v) return tonumber(v) or 0 end local function gems() return shadowNum(player:FindFirstChild("Gems") and player.Gems.Value) end local function level() return shadowNum(player:FindFirstChild("level") and player.level.Value) end local function rebirths() return shadowNum(statValue("Rebirths")) end local function cap() local c = player:FindFirstChild("maxPetCapacity") return c and c.Value or 20 end -- every crystal, cheapest first, with its real drop pool local crystals = {} do local prices = RS:FindFirstChild("crystalPrices") local chances = RS:FindFirstChild("crystalChances") if prices then for _, c in ipairs(prices:GetChildren()) do local base = c.Name:gsub(" Crystal$", "") local p, t = c:FindFirstChild("price"), c:FindFirstChild("priceType") local entry = { base = base, price = (p and p.Value) or math.huge, ptype = (t and t.Value) or "Gems", pool = {} } local ch = chances and chances:FindFirstChild(c.Name) if ch then for _, item in ipairs(ch:GetChildren()) do local n = item:FindFirstChild("nameValue") local r = item:FindFirstChild("rarityValue") local cv = item:FindFirstChild("chanceValue") if n then table.insert(entry.pool, { name = n.Value, rarity = r and r.Value or "?", chance = (cv and cv.Value) or 0 }) end end table.sort(entry.pool, function(a, b) return a.chance < b.chance end) end table.insert(crystals, entry) end table.sort(crystals, function(a, b) return a.price < b.price end) end end local function crystalByName(base) if base == nil then return nil end base = tostring(base):gsub("%s*Crystal%s*$", "") for _, c in ipairs(crystals) do if c.base == base or (c.base .. " Crystal") == base then return c end end return nil end local function crystalLabel(c) local parts = {} for _, p in ipairs(c.pool) do table.insert(parts, string.format("%s %s %d%%", p.name, p.rarity, p.chance)) end return table.concat(parts, ", ") end -- real pet/trail prices: cPetShopFolder prices every pet and trail local priceOf = {} do local shop = RS:FindFirstChild("cPetShopFolder") if shop then for _, item in ipairs(shop:GetChildren()) do local p = item:FindFirstChild("priceValue") local r = item:FindFirstChild("rarityValue") if p and not priceOf[item.Name] then priceOf[item.Name] = { price = p.Value, rarity = r and r.Value or "?" } end end end end local orbs = {} -- orb name -> { stat = "Steps"/"Gems", base = n } do local folder = RS:FindFirstChild("Orbs") if folder then for _, m in ipairs(folder:GetChildren()) do local v = m:FindFirstChildOfClass("IntValue") or m:FindFirstChildOfClass("NumberValue") if v then orbs[m.Name] = { stat = v.Name, base = v.Value } end end end end -- ============ ITEM HELPERS ============ local function folders() return { pets = player:FindFirstChild("petsFolder"), trails = player:FindFirstChild("trailsFolder"), } end local function itemRows(kind) local f = folders()[kind] local rows = {} if not f then return rows end for _, tier in ipairs(f:GetChildren()) do for _, it in ipairs(tier:GetChildren()) do rows[#rows + 1] = { it = it, tier = tier.Name, name = it.Name, kind = kind } end end return rows end local function itemCount(kind) local f = folders()[kind] local n = 0 if not f then return 0 end for _, tier in ipairs(f:GetChildren()) do n = n + #tier:GetChildren() end return n end -- what selling an item actually pays, level and evolution included local function sellValueOf(row) if not GF then return 0 end local lvl = row.it:FindFirstChild("level") return GF.calculatePetValue(row.tier, lvl and lvl.Value or 1, row.it:FindFirstChild("evolved") ~= nil) end local function equippedSet() local set = {} local eq = player:FindFirstChild("equippedPets") if eq then for _, c in ipairs(eq:GetChildren()) do local ref = c:FindFirstChild("petReference") if ref and ref.Value then set[ref.Value] = true end end end return set end local function equippedTrail() local t = player:FindFirstChild("equippedTrail") return t and t.Value or nil end -- ============ SELL / KEEP POLICY ============ -- Every sell decision goes through buildKeepSet, so "what gets sold" is one -- readable rule instead of scattered conditions: -- 1. NEVER sell the equipped pets, the equipped trail, or anything named in -- state.keepNames. -- 2. NEVER sell evolved pets/trails (state.keepEvolved) - each one cost five -- non-evolved copies, verified below. -- 3. ALWAYS sell anything below the rarity floor (state.sellBelow) unless it is -- hard-protected (equipped, named in keepNames, or evolved with keepEvolved -- on) - a floor is for dumping fodder, not for destroying a kept pet. -- 4. KEEP UP TO state.keepDupes copies of a name in state.keepNames - that is -- evolve fodder, and selling it is what silently kills auto-evolve. -- 5. KEEP the best state.keepTop items by the selected ranking. -- 6. Sell the lowest-ranked survivors, worst first, when a slot is needed. local RARITY_ORDER = { Basic = 1, Advanced = 2, Rare = 3, Epic = 4, Unique = 5, Omega = 6 } local sellStats = { pets = 0, trails = 0, gems = 0 } state.rankMode = state.rankMode or "MARKET" state.sellBelow = state.sellBelow or "Basic" if state.keepEvolved == nil then state.keepEvolved = true end state.keepDupes = state.keepDupes or 5 if state.evolveAll == nil then state.evolveAll = true end if state.autoEvolve == nil then state.autoEvolve = false end -- ranking is selectable, because "best" means different things here: -- MARKET = ReplicatedStorage.cPetShopFolder priceValue (rarity/desirability) -- VALUE = what selling it actually pays, globalFunctions.calculatePetValue -- RARITY = tier first (Omega > Unique > Epic > Rare > Advanced > Basic) local function marketOf(row) local p = priceOf[row.name] return p and p.price or 0 end local function rarityOf(row) local p = priceOf[row.name] return p and p.rarity or "Basic" end local function rankOf(row) if state.rankMode == "VALUE" then return sellValueOf(row) end if state.rankMode == "RARITY" then return (RARITY_ORDER[rarityOf(row)] or 0) * 1000000 + sellValueOf(row) end return marketOf(row) end local function isEvolved(row) local e = row.it:FindFirstChild("evolved") return e ~= nil and e.Value == true end local function isProtected(row) if row.kind == "pets" and equippedSet()[row.it] then return true end if row.kind == "trails" and equippedTrail() == row.it then return true end if state.keepNames[row.name] then return true end return false end -- worst first, so index 1 is the next candidate to sell local function rankedWorstFirst(kind) local rows = itemRows(kind) table.sort(rows, function(a, b) local ra, rb = rankOf(a), rankOf(b) if ra == rb then return sellValueOf(a) < sellValueOf(b) end return ra < rb end) return rows end local function buildKeepSet(kind) local rows = rankedWorstFirst(kind) local keep, dupes = {}, {} local topKept = 0 local floor = RARITY_ORDER[state.sellBelow] or 0 for i = #rows, 1, -1 do -- best first local row = rows[i] local protected = isProtected(row) local evo = isEvolved(row) local belowFloor = (RARITY_ORDER[rarityOf(row)] or 0) < floor if protected or (state.keepEvolved and evo) then keep[row.it] = true elseif belowFloor then -- forced out, never kept (step 3) elseif state.keepNames[row.name] then dupes[row.name] = dupes[row.name] or 0 if dupes[row.name] < state.keepDupes then dupes[row.name] = dupes[row.name] + 1 keep[row.it] = true end elseif topKept < state.keepTop then topKept = topKept + 1 keep[row.it] = true end end return keep end local function nextToSell(kind) local keep = buildKeepSet(kind) for _, row in ipairs(rankedWorstFirst(kind)) do if not keep[row.it] then return row end end return nil end -- what the policy would sell next, for the GUI (answers "what gets auto sold") local function sellPreview(kind, n) local keep = buildKeepSet(kind) local out = {} for _, row in ipairs(rankedWorstFirst(kind)) do if not keep[row.it] then table.insert(out, string.format("%s (%s, %d)", row.name, rarityOf(row), sellValueOf(row))) if #out >= (n or 3) then break end end end return out end local function sellRow(row) if row.kind == "pets" then if sellPetEvent then sellPetEvent:FireServer("sellPet", row.it) end sellStats.pets = sellStats.pets + 1 else if sellTrailEvent then sellTrailEvent:FireServer("sellTrail", row.it) end sellStats.trails = sellStats.trails + 1 end return true end local function sellLowest(kind) local row = nextToSell(kind) if not row then return nil end sellRow(row) return row end -- sell a freshly hatched item unless the policy says it is a keeper local function sellIfJunk(row) if not state.autoSell or not row then return false end local keep = buildKeepSet(row.kind) if keep[row.it] then return false end local g0 = gems() sellRow(row) sellStats.gems = sellStats.gems + math.max(gems() - g0, 0) return true end -- keep `state.keepFree` slots free on both pets and trails local function ensureRoom(need) need = need or state.keepFree local freed = 0 local capNow = cap() for _ = 1, 30 do if capNow - itemCount("pets") >= need then break end if not sellLowest("pets") then break end freed = freed + 1 task.wait(0.08) end for _ = 1, 30 do if capNow - itemCount("trails") >= need then break end if not sellLowest("trails") then break end freed = freed + 1 task.wait(0.08) end return freed end -- ============ EVOLVE ENGINE ============ -- Verified live: evolving needs FIVE non-evolved copies of the same name, and the -- server consumes four of them, leaving one marked evolved. -- pets : petEvolveEvent:FireServer("evolvePet", petName) -- trails: evolveTrailEvent:FireServer("evolveTrail", trailInstance) -- Measured: 5x Red Bunny (1,500 gems each from the pet shop) -> pets 14 -> 10 with -- one evolved Red Bunny left; 5x Red Trail -> trails 19 -> 15 with one evolved. -- globalFunctions.getNumberOfPets / getNumberOfSameTrail count only NON-evolved -- copies, which is exactly the number the /5 gate wants. local evolveStats = { pets = 0, trails = 0, last = {} } local function evolveCounts(kind) local counts = {} for _, row in ipairs(itemRows(kind)) do if not isEvolved(row) then counts[row.name] = (counts[row.name] or 0) + 1 end end return counts end local function evolveScan(kind, onlyKeepList) local counts = evolveCounts(kind) local done = {} local names = {} for name, n in pairs(counts) do if n >= 5 then table.insert(names, name) end end table.sort(names) for _, name in ipairs(names) do if state.evolveAll or not onlyKeepList or state.keepNames[name] then if kind == "pets" then if petEvolveEvent then petEvolveEvent:FireServer("evolvePet", name) end evolveStats.pets = evolveStats.pets + 1 else -- trails take the instance, so hand over a non-evolved copy for _, row in ipairs(itemRows("trails")) do if row.name == name and not isEvolved(row) then if evolveTrailEvent then evolveTrailEvent:FireServer("evolveTrail", row.it) end break end end evolveStats.trails = evolveStats.trails + 1 end evolveStats.last[kind .. "/" .. name] = (evolveStats.last[kind .. "/" .. name] or 0) + 1 table.insert(done, name) task.wait(0.9) end end return done end local function evolveNow() local a = evolveScan("pets") local b = evolveScan("trails") return a, b end local function evolveLoop() while (state.autoEvolve or state.evolvePets or state.evolveTrails) and isCurrent() do local fired = {} if state.autoEvolve or state.evolvePets then for _, n in ipairs(evolveScan("pets")) do table.insert(fired, "pet " .. n) end end if state.autoEvolve or state.evolveTrails then for _, n in ipairs(evolveScan("trails")) do table.insert(fired, "trail " .. n) end end if #fired > 0 then setStatus("Evolved: " .. table.concat(fired, ", "), MONO.rare) notify("Evolved " .. #fired .. " item type(s)", "good") else local ready = {} for kind, label in pairs({ pets = "pets", trails = "trails" }) do local best, bestName = 0, nil for name, n in pairs(evolveCounts(kind)) do if n > best and n >= 2 then best, bestName = n, name end end if bestName then ready[#ready + 1] = string.format("%s %s %d/5", label, bestName, best) end end setStatus(#ready > 0 and ("Evolve watch: " .. table.concat(ready, ", ")) or "Evolve watch: no duplicates yet", MONO.sub) end task.wait(3) end state.autoEvolve, state.evolvePets, state.evolveTrails = false, false, false state.evolveThread = nil end -- ============ EQUIP ENGINE ============ -- The game refuses an equip when every slot is taken (findEmptyPetSlot == nil), -- so v5 unequips the weakest equipped pet first, then equips. Verified: equipping -- into a freed slot lands (pet1 went from Unique/Speedy Sensei to Omega/Soul Fusion Dog). local equipStats = { equipped = 0, unequipped = 0 } local function drainTotals() local t = 0 local rows = itemRows("pets") for _, r in ipairs(rows) do t = t + sellValueOf(r) end return t end local function autoEquipStep() local eq = player:FindFirstChild("equippedPets") if not eq then return 0 end local slots = #eq:GetChildren() if slots == 0 then slots = 4 end local rows = itemRows("pets") if #rows == 0 then return 0 end table.sort(rows, function(a, b) local ra, rb = rankOf(a), rankOf(b) if ra == rb then return sellValueOf(a) > sellValueOf(b) end return ra > rb end) local have = equippedSet() local actions = 0 local wanted = math.min(slots, #rows) for i = 1, wanted do local row = rows[i] if not have[row.it] then -- free a slot if the list is full local occupied = 0 for _ in ipairs(eq:GetChildren()) do occupied = occupied + 1 end if occupied >= slots then -- weak link: lowest rank that is currently equipped local worst for _, c in ipairs(eq:GetChildren()) do local ref = c:FindFirstChild("petReference") if ref and ref.Value then local rk = priceOf[ref.Value.Name] and priceOf[ref.Value.Name].price or 0 if not worst or rk < worst.rank then worst = { ref = ref, rank = rk } end end end if worst and worst.rank < rankOf(row) and equipPetEvent then equipPetEvent:FireServer("unequipPet", worst.ref.Value) equipStats.unequipped = equipStats.unequipped + 1 task.wait(0.35) else break end end if equipPetEvent then equipPetEvent:FireServer("equipPet", row.it) equipStats.equipped = equipStats.equipped + 1 actions = actions + 1 task.wait(0.35) end end end return actions end local function equipLoop() while state.autoEquip and isCurrent() do local n = autoEquipStep() setStatus(n > 0 and ("Equipped " .. n .. " pet(s)") or "Equipped pets already optimal", MONO.accent) task.wait(5) end state.autoEquip = false state.equipThread = nil end -- ============ HATCH ENGINE (v5 flagship) ============ -- The whole game-side hatch is: -- openCrystalRemote:InvokeServer("openCrystal", " Crystal") -- Returns (petName, rarity, imageId, extra). Cost is crystalPrices[name].price in -- Gems. No cooldown was measured; capacity (pets AND trails under maxPetCapacity) -- is the only real gate, which is why the sell engine runs inside this loop. local hatchStats = { attempts = 0, pets = 0, trails = 0, gems = 0, failed = 0 } local function crystalFor(base) local c = crystalByName(base) if c then return c end return crystals[#crystals] end -- most expensive crystal we can still afford above the gem reserve local function bestAffordable() for i = #crystals, 1, -1 do local c = crystals[i] if c.ptype == "Gems" and gems() - c.price >= state.gemReserve then return c.base end end return nil end -- how many hatches, on average, to land a named pet from this crystal local function oddsFor(base, petName) local c = crystalByName(base) if not c then return nil end for _, p in ipairs(c.pool) do if p.name == petName then local chance = math.max(p.chance, 1) return { chance = p.chance, rarity = p.rarity, expected = math.ceil(100 / chance), cost = math.ceil(100 / chance) * c.price } end end return nil end local function hatchOnce() local base = state.hatchChoice if base == "Auto" then base = bestAffordable() if not base then return false, "nothing affordable above the reserve" end end local c = crystalFor(base) if not c then return false, "no such crystal: " .. tostring(base) end if c.ptype == "Gems" and gems() - c.price < state.gemReserve then return false, "gem reserve (" .. shorten(state.gemReserve) .. ")" end if state.autoSell then ensureRoom(state.keepFree) end if cap() - itemCount("pets") < 1 or cap() - itemCount("trails") < 1 then return false, "capacity full, nothing left to sell" end local beforePets, beforeTrails = {}, {} for _, r in ipairs(itemRows("pets")) do beforePets[r.it] = true end for _, r in ipairs(itemRows("trails")) do beforeTrails[r.it] = true end local g0 = gems() local name, rarity = openCrystalRemote:InvokeServer("openCrystal", c.base .. " Crystal") hatchStats.attempts = hatchStats.attempts + 1 hatchStats.gems = hatchStats.gems + (g0 - gems()) state.hatches = state.hatches + 1 if name == nil then hatchStats.failed = hatchStats.failed + 1 return false, "server refused (capacity or price)" end state.rarity[rarity or "?"] = (state.rarity[rarity or "?"] or 0) + 1 -- what did we actually get? local got for _, r in ipairs(itemRows("pets")) do if not beforePets[r.it] then got = r break end end if got then hatchStats.pets = hatchStats.pets + 1 got.kind = "pets" else for _, r in ipairs(itemRows("trails")) do if not beforeTrails[r.it] then got = r break end end if got then hatchStats.trails = hatchStats.trails + 1 end end -- sniper bookkeeping if state.snipePet ~= "" and name == state.snipePet then state.hits[state.snipePet] = (state.hits[state.snipePet] or 0) + 1 notify((state.snipePet .. " hatched! (" .. shorten(c.price) .. " gems each)"), "good") end -- keep the good ones, sell the rest (policy lives in buildKeepSet) if state.autoSell and got then sellIfJunk(got) end return true, nil, name, rarity, c.base end local function hatchLoop() local carry, shownRate = 0, math.max(state.hatchRate, 1) local lastReport, statusAcc = 0, 0 local started = os.clock() while state.hatcher and isCurrent() do local dt = RunService.Heartbeat:Wait() shownRate = math.max(state.hatchRate, 1) carry = math.min(carry + shownRate * dt, 12) local budget = math.floor(carry) carry = carry - budget local stopReason = nil for _ = 1, budget do if not state.hatcher then break end if state.hatchMax > 0 and hatchStats.attempts >= state.hatchMax then stopReason = "reached " .. state.hatchMax .. " hatches" state.hatcher = false break end local ok, err, name = hatchOnce() if not ok then if err and err:find("capacity") then -- genuinely full and nothing sellable: wait for a sell task.wait(0.5) elseif err == "server refused (capacity or price)" then stopReason = "server refused (capacity or price)" state.hatcher = false break elseif err and (err:find("reserve") or err:find("affordable")) then stopReason = err state.hatcher = false break end else statusAcc = statusAcc + 1 if statusAcc >= 5 or os.clock() - lastReport > 1 then statusAcc, lastReport = 0, os.clock() local rar = {} for k, v in ipairs({ "Omega", "Unique", "Epic", "Rare", "Advanced", "Basic" }) do if state.rarity[v] then table.insert(rar, v .. " " .. state.rarity[v]) end end setStatus(string.format("%s: %d hatches %.1f/s %s", state.hatchChoice, state.hatches, shownRate, table.concat(rar, " / ")), MONO.success) end end end if stopReason then setStatus("Hatch stopped: " .. stopReason, MONO.warn) end end state.hatcher = false state.hatchThread = nil local dt = math.max(os.clock() - started, 1) setStatus(string.format("Hatch farm stopped: %d hatches in %.0fs (%.2f/s), %s gems", hatchStats.attempts, dt, hatchStats.attempts / dt, shorten(hatchStats.gems)), MONO.warn) end local function toggleHatch(choice) choice = choice or state.hatchChoice or "Auto" if choice ~= "Auto" then local c = crystalByName(choice) if c then choice = c.base end end if state.hatcher and state.hatchChoice == choice then state.hatcher = false setStatus("Hatcher stopping...", MONO.sub) return end state.hatchChoice = choice state.hatcher = true if not state.hatchThread then state.hatchThread = spawnLoop(hatchLoop) end end -- ============ ORB FARM (unchanged from v4, verified) ============ -- oScript fires exactly this on touch: orbEvent:FireServer("collectOrb", orbName, currentMap.Value) local function currentMap() local name = player:FindFirstChild("currentMap") name = name and name.Value or "City" local f = workspace:FindFirstChild("orbFolder") if f and f:FindFirstChild(name) then return name end if f then for _, c in ipairs(f:GetChildren()) do if #c:GetChildren() > 0 then return c.Name end end end return name end local ORB_BURST_CAP = 400 local function orbLoop() local fired = 0 local map = currentMap() local names = {} for name in pairs(orbs) do table.insert(names, name) end if #names == 0 then names = { "Red Orb" } end table.sort(names) local function orbName() if state.orbChoice == "ALL" then return names[(fired % #names) + 1] end return state.orbChoice or names[1] end local carry, sinceStatus = 0, 0 while state.orbFarming and isCurrent() do local dt = RunService.Heartbeat:Wait() carry = math.min(carry + math.max(state.orbRate, 1) * dt, ORB_BURST_CAP) local budget = math.floor(carry) carry = carry - budget if budget >= 1 then for _ = 1, budget do if orbEvent then orbEvent:FireServer("collectOrb", orbName(), map) end fired = fired + 1 if not state.orbInfinite and fired >= state.orbAmount then break end end sinceStatus = sinceStatus + dt if sinceStatus > 0.25 then sinceStatus = 0 setStatus(string.format("%s x%d (%.0f/s) %s", orbName(), fired, state.orbRate, state.orbInfinite and "infinite" or (fired .. "/" .. state.orbAmount)), MONO.success) end end if not state.orbInfinite and fired >= state.orbAmount then break end end state.orbFarming = false state.orbThread = nil setStatus("Orb farm stopped after " .. fired .. " calls", MONO.warn) end local function toggleOrb(choice) if state.orbFarming and state.orbChoice == choice then state.orbFarming = false setStatus("Orb farm stopping...", MONO.sub) return end state.orbFarming = true state.orbChoice = choice if not state.orbThread then state.orbThread = spawnLoop(orbLoop) end end -- ============ HOOP LOOP (unchanged from v4, verified) ============ -- hoopEvent is server -> client only; the payout happens when the server sees the -- character touch a hoop, so the lever is relocating Workspace.Hoops onto us. local function hrp() local ch = player.Character return ch and ch:FindFirstChild("HumanoidRootPart") end local function hoopLoop() local home = {} local passes = 0 while state.hoopLoop and isCurrent() do local root = hrp() if not root then task.wait(0.5) continue end local folder = workspace:FindFirstChild("Hoops") local list = {} if folder then for _, h in ipairs(folder:GetChildren()) do if h:IsA("BasePart") then if not home[h] then home[h] = h.CFrame end table.insert(list, h) end end end if #list == 0 then task.wait(1) continue end local before = statValue("Hoops") local anchor = root.Position for _, h in ipairs(list) do h.CFrame = CFrame.new(anchor + Vector3.new(0, 2, 0)) end task.wait(1.0) for _, h in ipairs(list) do local o = home[h] if o then h.CFrame = o end end passes = passes + 1 setStatus(string.format("Hoops +%d this pass pass %d (8s debounce each)", statValue("Hoops") - before, passes), MONO.success) task.wait(7.6) end for h, o in pairs(home) do pcall(function() h.CFrame = o end) end state.hoopLoop = false state.hoopThread = nil setStatus("Hoop loop stopped", MONO.warn) end -- ============ QUEST AUTO-COLLECT (unchanged from v4, verified) ============ -- questsScript fires questsEvent:FireServer("collectQuest", questInstance) local function collectQuests() local collected = 0 local qroot = player:FindFirstChild("Quests") if not qroot then return 0 end for _, folderName in ipairs({ "Daily Quests", "Weekly Quests", "Story Quests", "completedQuests" }) do local folder = qroot:FindFirstChild(folderName) if folder then for _, q in ipairs(folder:GetChildren()) do if q:IsA("Folder") then local reqs = q:FindFirstChild("requirements") if reqs and #reqs:GetChildren() > 0 then local done = true for _, r in ipairs(reqs:GetChildren()) do if r:IsA("ValueBase") then local p = r:FindFirstChild("progress") if p and p.Value < r.Value then done = false end end end if done and questsEvent then questsEvent:FireServer("collectQuest", q) collected = collected + 1 state.session.quests = (state.session.quests or 0) + 1 task.wait(0.6) end end end end end end return collected end local function questLoop() while state.quest and isCurrent() do local n = collectQuests() setStatus(n > 0 and ("Collected " .. n .. " quest(s)") or "Quest watch: nothing complete yet", n > 0 and MONO.success or MONO.sub) task.wait(5) end state.quest = false state.questThread = nil end -- ============ SPEED RAMPS (unverified reward, kept for ramp quests) ============ local function rampLoop() local ramps = workspace:FindFirstChild("speedRamps") local index, tries = 1, 0 while state.ramps and isCurrent() do if not ramps or #ramps:GetChildren() == 0 then setStatus("No speedRamps found", MONO.danger) task.wait(3) else local list = ramps:GetChildren() local model = list[((index - 1) % #list) + 1] index = index + 1 local touch for _, d in ipairs(model:GetDescendants()) do if d:IsA("BasePart") and d.Name == "touchPart" then touch = d break end end local root = hrp() if touch and root then root.CFrame = CFrame.new(touch.Position + Vector3.new(0, 3, 0)) if type(firetouchinterest) == "function" then pcall(function() firetouchinterest(root, touch, 0) end) task.wait(0.05) pcall(function() firetouchinterest(root, touch, 1) end) end if speedRampEvent then speedRampEvent:FireServer("usedRamp") end tries = tries + 1 setStatus("Ramp touches: " .. tries .. " (unverified)", MONO.warn) end end task.wait(0.5) end state.ramps = false state.rampThread = nil end -- ============ AUTO REBIRTH (new) ============ -- confirmButton in gameGuiScript: rebirthEvent:FireServer("rebirthRequest") once -- level >= calculateRequiredRebirthLevel(Rebirths.Value, player). local function requiredRebirthLevel() if not GF then return nil end local ok, res = pcall(function() return GF.calculateRequiredRebirthLevel(rebirths(), player) end) return ok and res or nil end local function rebirthLoop() while state.autoRebirth and isCurrent() do local req = requiredRebirthLevel() if req and level() >= req then if rebirthEvent then rebirthEvent:FireServer("rebirthRequest") end state.rebirthsDone = state.rebirthsDone + 1 setStatus(string.format("REBIRTH sent (%d this session, was level %d >= %d)", state.rebirthsDone, level(), req), MONO.rare) task.wait(6) else setStatus(string.format("Auto rebirth: level %d / %d needed (rebirths %d)", level(), req or -1, rebirths()), MONO.sub) task.wait(5) end end state.autoRebirth = false state.rebirthThread = nil end -- ============ ULTIMATE UPGRADES (new) ============ -- Los.secondGameGuiScript: ultimatesRemote:InvokeServer("upgradeUltimate", name) -- costs Rebirths (globalFunctions.calculateUltimateRebirthCost), max 3-10 each. local ULTIMATE_PRIORITY = { "Step Booster", "Gem Booster", "Ethereal Orbs", "Demon Hoops", "Infernal Gems", "+1 Pet Slot", "+10 Item Capacity", "x2 Chest Rewards", "x2 Quest Rewards", "x2 Trail Boosts", "Divine Rebirth", "+1 Daily Spin", } local function ultimateProgress(name) local owned = player:FindFirstChild("ultimatesFolder") and player.ultimatesFolder:FindFirstChild(name) local def = RS:FindFirstChild("gameUltimatesFolder") and RS.gameUltimatesFolder:FindFirstChild(name) local have = owned and owned.Value or 0 local maxUp = def and def:FindFirstChild("maxUpgrades") local cost = nil if GF and def then local ok, res = pcall(function() return GF.calculateUltimateRebirthCost(def, have) end) if ok then cost = res end end return have, maxUp and maxUp.Value or 0, cost end local function ultimateLoop() while state.autoUltimates and isCurrent() do local bought = 0 for _, name in ipairs(ULTIMATE_PRIORITY) do if not state.autoUltimates then break end local have, maxUp, cost = ultimateProgress(name) if cost and have < maxUp and rebirths() >= cost + state.rebirthReserve then local ok, res = pcall(function() return ultimatesRemote:InvokeServer("upgradeUltimate", name) end) if ok and res == true then state.ultimatesBought = state.ultimatesBought + 1 bought = bought + 1 setStatus(string.format("Ultimate bought: %s (%d/%d) for %d rebirths", name, have + 1, maxUp, cost), MONO.rare) task.wait(1.2) else setStatus("Ultimate refused: " .. name, MONO.warn) task.wait(1.2) end end end if bought == 0 then setStatus(string.format("Ultimates: nothing affordable (rebirths %d, reserve %d)", rebirths(), state.rebirthReserve), MONO.sub) end task.wait(4) end state.autoUltimates = false state.ultimateThread = nil end -- ============ CHEST COLLECTOR (new) ============ -- gameGuiScript: checkChestRemote:InvokeServer("Golden Chest") etc., 6h cooldown, -- returns (ok, currency, amount). Touching circleInner is what the game wires up, -- so we stand on it and also invoke directly. local CHESTS = { { label = "Golden Chest", model = "goldenChest" }, { label = "Enchanted Chest", model = "enchantedChest" }, { label = "Magma Chest", model = "magmaChest" }, { label = "Jungle Chest", model = "jungleChest" }, } local function chestLoop() while state.chests and isCurrent() do local got = 0 for _, chest in ipairs(CHESTS) do if not state.chests then break end local model = workspace:FindFirstChild(chest.model) local inner = model and model:FindFirstChild("circleInner", true) if inner and hrp() then hrp().CFrame = CFrame.new(inner.Position + Vector3.new(0, 3, 0)) task.wait(0.4) end local ok, res, currency, amount = pcall(function() return checkChestRemote:InvokeServer(chest.label) end) if ok and res == true then state.chestsOpened = state.chestsOpened + 1 got = got + 1 setStatus(string.format("%s: +%s %s", chest.label, shorten(amount or 0), tostring(currency)), MONO.rare) end task.wait(0.5) end if got == 0 then setStatus("Chests: nothing ready (6h cooldown)", MONO.sub) end task.wait(20) end state.chests = false state.chestThread = nil end -- ===================================================================== -- UI LAYER - Project UAI UI LIB window -- (v5 farm core above is unchanged; this replaces the hand-built HUD) -- ===================================================================== local UAI_URL = "https://raw.githubusercontent.com/Project-Ptolemy/ProjectUAI/main/dist/uai-ui.lua" local libOk, UI = pcall(function() return loadstring(game:HttpGet(UAI_URL))() end) if not libOk or type(UI) ~= "table" or type(UI.CreateWindow) ~= "function" then error("[LOS Autofarm v5] could not load the Project UAI UI LIB from " .. UAI_URL) end local window = UI:CreateWindow({ Id = "los-autofarm-v5", Title = "LOS Autofarm v5", Subtitle = "Legends of Speed - direct remote farm", GameName = "Legends of Speed", Width = 840, Height = 640, }) -- every control handle the refresh loop needs local U = {} local function toneOf(colour) if colour == nil then return "Info" end if colour == MONO.success then return "Success" end if colour == MONO.warn then return "Warning" end if colour == MONO.danger then return "Danger" end return "Info" end setStatus = function(text, colour) text = tostring(text or "") U.tone = toneOf(colour) if U.status then U.status:SetText(text) end if U.badge then U.badge:Set(U.tone) end end notify = function(text, tone) local kind = "Info" if tone == "good" then kind = "Success" elseif tone == "bad" then kind = "Danger" elseif tone == "warn" then kind = "Warning" end window:Notify({ Title = "Autofarm", Content = tostring(text or ""), Kind = kind, Duration = 4, }) end -- ============ SESSION STATS ============ local function itemTotal(kind) local n = 0 for _, r in ipairs(itemRows(kind)) do n = n + 1 end return n end state.session = { t = os.clock(), steps = statValue("Steps"), gems = gems(), exp = statValue("exp"), hoops = statValue("Hoops"), level = level(), rebirths = rebirths(), pets = itemTotal("pets"), trails = itemTotal("trails"), hatches = 0, } local function sessionText() local s = state.session local dt = math.max(os.clock() - s.t, 1) local m = dt / 60 local best = "" for name, count in pairs(state.hits) do best = name .. " x" .. count break end return string.format( "Steps %s (%s/m) Gems %s (%s/m)\nLevel %d Rebirths %d Pets %d/%d Trails %d/%d\nHatches %d (%.1f/m) Gems spent %s Sold %d pets / %d trails%s", shorten(statValue("Steps")), shorten((statValue("Steps") - s.steps) / m), shorten(gems()), shorten((gems() - s.gems) / m), level(), rebirths(), itemTotal("pets"), cap(), itemTotal("trails"), cap(), hatchStats.attempts, hatchStats.attempts / m, shorten(hatchStats.gems), sellStats.pets, sellStats.trails, best ~= "" and ("\nSniper: " .. best) or "") end -- ============ LOOP STARTERS (single place, shared by UI and API sync) ============ local function hatchSet(on, choice) choice = choice or state.hatchChoice or "Auto" if on then if state.hatcher and state.hatchChoice ~= choice then toggleHatch(choice) elseif not state.hatcher then toggleHatch(choice) end elseif state.hatcher then toggleHatch(state.hatchChoice) end state.hatchChoice = choice end local function orbSet(on, choice) choice = choice or state.orbChoice or "ALL" if on then if not (state.orbFarming and state.orbChoice == choice) then toggleOrb(choice) end elseif state.orbFarming then toggleOrb(state.orbChoice) end end local function evolveApply() if state.autoEvolve then state.evolvePets, state.evolveTrails = true, true end if (state.autoEvolve or state.evolvePets or state.evolveTrails) and not state.evolveThread then state.evolveThread = spawnLoop(evolveLoop) end end local function equipApply() if state.autoEquip and not state.equipThread then state.equipThread = spawnLoop(equipLoop) end end -- flag name, its thread handle name, and the loop body local SIMPLE_LOOPS = { { "hoop", "hoopLoop", "hoopThread", hoopLoop }, { "quest", "quest", "questThread", questLoop }, { "ramps", "ramps", "rampThread", rampLoop }, { "rebirth", "autoRebirth", "rebirthThread", rebirthLoop }, { "ultimates", "autoUltimates", "ultimateThread", ultimateLoop }, { "chests", "chests", "chestThread", chestLoop }, } local function simpleApply(flagKey, threadKey, fn, on) state[flagKey] = on and true or false if state[flagKey] and not state[threadKey] then state[threadKey] = spawnLoop(fn) end end -- push state back into the controls (the ;los plugin and the API can flip -- flags without going through the UI, so the window follows state) local function syncControls() if not U.hatchRun then return end local ok, err = pcall(function() U.hatchRun:Set(state.hatcher and true or false, true) U.crystal:Set(state.hatchChoice, true) U.orbRun:Set(state.orbFarming and true or false, true) U.orbChoice:Set(state.orbChoice or "ALL", true) U.orbAmount:SetDisabled(state.orbInfinite) U.hoop:Set(state.hoopLoop and true or false, true) U.quest:Set(state.quest and true or false, true) U.equipSet:Set(state.autoEquip and true or false, true) U.evolveAll:Set(state.autoEvolve and true or false, true) U.evolvePets:Set(state.evolvePets and not state.autoEvolve and true or false, true) U.evolveTrails:Set(state.evolveTrails and not state.autoEvolve and true or false, true) for _, L in ipairs(SIMPLE_LOOPS) do if U[L[1]] then U[L[1]]:Set(state[L[2]] and true or false, true) end end -- keep-list field: mirror the live list unless the user is typing in it if U.keepName then local names = {} for name in pairs(state.keepNames) do table.insert(names, name) end table.sort(names) local mirror = table.concat(names, ", ") local shown = tostring(U.keepName:Get() or "") if shown == "" or shown == (U.keepMirror or "") then U.keepMirror = mirror U.keepName:Set(mirror, true) end end end) if not ok and not U.syncWarned then U.syncWarned = true warn("[LOS Autofarm v5] control sync error: " .. tostring(err)) end end -- read every control back into state and reconcile the workers local function applyControls() if not U.hatchRun then return end state.autoSell = U.autoSell:Get() state.rankMode = U.rankMode:Get() state.keepTop = U.keepTop:Get() state.keepDupes = U.keepDupes:Get() state.keepFree = U.keepFree:Get() state.keepEvolved = U.keepEvolved:Get() state.evolveAll = not U.evolveOnlyKept:Get() local floor = U.floor:Get() state.sellBelow = (floor == "OFF" or floor == nil) and "Basic" or floor state.hatchRate = U.hatchRate:Get() state.gemReserve = U.gemReserve:Get() state.hatchMax = U.hatchMax:Get() state.orbRate = U.orbRate:Get() state.orbAmount = U.orbAmount:Get() state.orbInfinite = U.orbInfinite:Get() state.rebirthReserve = U.rebirthReserve:Get() hatchSet(U.hatchRun:Get() and true or false, U.crystal:Get()) orbSet(U.orbRun:Get() and true or false, U.orbChoice:Get()) U.orbAmount:SetDisabled(state.orbInfinite) for _, L in ipairs(SIMPLE_LOOPS) do if U[L[1]] then simpleApply(L[2], L[3], L[4], U[L[1]]:Get()) end end state.autoEvolve = U.evolveAll:Get() state.evolvePets = U.evolvePets:Get() state.evolveTrails = U.evolveTrails:Get() if not state.autoEvolve then if not U.evolvePets:Get() then state.evolvePets = false end if not U.evolveTrails:Get() then state.evolveTrails = false end end evolveApply() state.autoEquip = U.equipSet:Get() equipApply() syncControls() end -- ============ TAB: HATCH ============ local hatchTab = window:Tab({ Id = "hatch", Title = "Hatch" }) local crystalOptions = { { Label = "Auto (most expensive affordable)", Value = "Auto" } } for _, c in ipairs(crystals) do table.insert(crystalOptions, { Label = string.format("%s - %s %s", c.base, shorten(c.price), c.ptype), Value = c.base, }) end local hSec = hatchTab:Section({ Title = "Hatch farm", Description = 'openCrystalRemote:InvokeServer("openCrystal", " Crystal") - no menu, no walk, no cooldown. Refused only at pet/trail capacity.', }) U.crystal = hSec:Dropdown({ Id = "hatch-crystal", Text = "Crystal", Options = crystalOptions, Default = state.hatchChoice, Searchable = true, Placeholder = "Pick a crystal", Callback = function(value) state.hatchChoice = value if state.hatcher then hatchSet(true, value) end end, }) U.hatchRun = hSec:Toggle({ Id = "hatch-running", Text = "Hatch loop", Default = state.hatcher, Description = "Start or stop hatching the selected crystal.", Callback = function(on) hatchSet(on and true or false) end, }) U.hatchRate = hSec:Slider({ Id = "hatch-rate", Text = "Hatches per second", Min = 1, Max = 20, Step = 1, Default = state.hatchRate, Callback = function(v) state.hatchRate = v end, }) U.gemReserve = hSec:Slider({ Id = "gem-reserve", Text = "Gem reserve", Min = 0, Max = 10000000, Step = 100000, Default = state.gemReserve, Suffix = " gems", Description = "Stops spending once gems drop under this.", Callback = function(v) state.gemReserve = v end, }) U.hatchMax = hSec:Slider({ Id = "hatch-max", Text = "Stop after N hatches (0 = forever)", Min = 0, Max = 5000, Step = 50, Default = state.hatchMax, Callback = function(v) state.hatchMax = v end, }) U.snipe = hSec:Input({ Id = "snipe-pet", Text = "Snipe pet name", Default = state.snipePet or "", Placeholder = "e.g. Swift Samurai", Description = "Logs the live odds and expected cost for this pet.", }) local function snipeCommit(value) value = tostring(value or "") state.snipePet = value if value == "" then notify("sniper cleared", "info") return end local base = state.hatchChoice ~= "Auto" and state.hatchChoice or "Jungle" local odds = oddsFor(base, value) if odds then notify(string.format("%s: %s %d%%, ~%d hatches (~%s gems)", value, odds.rarity, odds.chance, odds.expected, shorten(odds.cost)), "good") else notify("no crystal in the live tables drops " .. value, "warn") end end if type(U.snipe.OnCommit) == "function" then U.snipe:OnCommit(snipeCommit) else U.snipe:OnChanged(snipeCommit) end local dropSec = hatchTab:Section({ Title = "Live drop tables", Description = "Read from ReplicatedStorage.crystalChances at load.", Collapsible = true, Collapsed = true, }) do local lines = {} for _, c in ipairs(crystals) do if #c.pool > 0 then table.insert(lines, c.base .. ": " .. crystalLabel(c)) end end dropSec:Label({ Text = table.concat(lines, "\n") }) end -- ============ TAB: ORBS ============ local orbTab = window:Tab({ Id = "orbs", Title = "Orbs" }) local oSec = orbTab:Section({ Title = "Orb farm", Description = 'orbEvent:FireServer("collectOrb", name, map) - pays per call, no rate limit and no cooldown.', }) local orbOptions = { { Label = "ALL (rotate every payout type)", Value = "ALL" } } do local names = {} for name in pairs(orbs) do table.insert(names, name) end table.sort(names) for _, name in ipairs(names) do table.insert(orbOptions, { Label = string.format("%s -> %s", name, orbs[name].stat), Value = name, }) end end U.orbChoice = oSec:Dropdown({ Id = "orb-choice", Text = "Orb", Options = orbOptions, Default = state.orbChoice or "ALL", Searchable = true, Callback = function(value) state.orbChoice = value if state.orbFarming then orbSet(true, value) end end, }) U.orbRun = oSec:Toggle({ Id = "orb-running", Text = "Orb loop", Default = state.orbFarming, Callback = function(on) orbSet(on and true or false) end, }) U.orbRate = oSec:Slider({ Id = "orb-rate", Text = "Calls per second", Min = 5, Max = 5000, Step = 25, Default = state.orbRate, Callback = function(v) state.orbRate = v end, }) U.orbAmount = oSec:Slider({ Id = "orb-amount", Text = "Calls per payout", Min = 100, Max = 1000000, Step = 5000, Default = state.orbAmount, Disabled = state.orbInfinite, Callback = function(v) state.orbAmount = v end, }) U.orbInfinite = oSec:Toggle({ Id = "orb-infinite", Text = "Infinite calls per payout", Default = state.orbInfinite, Description = "Off uses the Calls per payout value above.", Callback = function(on) state.orbInfinite = on U.orbAmount:SetDisabled(on) end, }) oSec:Label({ Text = "measured per call: Red 3397 steps, Ethereal 5557 steps, Gem 2836 gems, Yellow 12 exp", }) -- ============ TAB: PETS ============ local petTab = window:Tab({ Id = "pets", Title = "Pets" }) local sSec = petTab:Section({ Title = "Sell policy", Description = "What gets auto-sold once an item is ranked. Equipped and evolved keepers are never sold.", }) U.autoSell = sSec:Toggle({ Id = "sell-auto", Text = "Auto-sell junk (keeps capacity free)", Default = state.autoSell, Callback = function(v) state.autoSell = v end, }) U.rankMode = sSec:Segmented({ Id = "sell-rank", Text = "Rank by", Options = { "MARKET", "VALUE", "RARITY" }, Default = state.rankMode, Callback = function(v) state.rankMode = v notify("ranking by " .. v, "info") end, }) U.keepTop = sSec:Slider({ Id = "sell-keeptop", Text = "Keep best N pets/trails", Min = 0, Max = 20, Step = 1, Default = state.keepTop, Callback = function(v) state.keepTop = v end, }) U.keepDupes = sSec:Slider({ Id = "sell-keepdupes", Text = "Keep N copies of a kept name (evolve fodder)", Min = 0, Max = 10, Step = 1, Default = state.keepDupes, Callback = function(v) state.keepDupes = v end, }) U.keepFree = sSec:Slider({ Id = "sell-keepfree", Text = "Free slots to hold open", Min = 1, Max = 10, Step = 1, Default = state.keepFree, Callback = function(v) state.keepFree = v end, }) U.keepEvolved = sSec:Toggle({ Id = "sell-keepevolved", Text = "Keep evolved pets/trails", Default = state.keepEvolved, Callback = function(v) state.keepEvolved = v end, }) U.evolveOnlyKept = sSec:Toggle({ Id = "sell-evolvekept", Text = "Evolve only kept names", Default = not state.evolveAll, Callback = function(v) state.evolveAll = not v evolveApply() end, }) U.floor = sSec:Segmented({ Id = "sell-floor", Text = "Rarity floor (keep this tier and above, dump below)", Options = { { Label = "OFF", Value = "OFF" }, { Label = "Basic", Value = "Basic" }, { Label = "Advanced", Value = "Advanced" }, { Label = "Rare", Value = "Rare" }, { Label = "Epic", Value = "Epic" }, { Label = "Unique", Value = "Unique" }, }, Default = (RARITY_ORDER[state.sellBelow] or 1) <= 1 and "OFF" or state.sellBelow, Callback = function(v) state.sellBelow = v == "OFF" and "Basic" or v notify(v == "OFF" and "rarity floor off" or ("keeping " .. v .. " and above, dumping below"), "info") end, }) U.keepName = sSec:Input({ Id = "sell-keepname", Text = "Keep list", Placeholder = "Add a name (empty clears the list)", Description = "Names here are never sold, and are used as evolve fodder.", }) sSec:Button({ Id = "sell-keepadd", Text = "Add to keep list", Callback = function() local text = tostring(U.keepName:Get() or "") text = text:gsub("^%s+", ""):gsub("%s+$", "") if text == "" then state.keepNames = {} notify("keep list cleared", "info") else state.keepNames[text] = true notify("kept: " .. text, "good") end U.keepName:Set("", true) end, }) sSec:Button({ Id = "sell-junk", Text = "Sell junk now", Description = "Frees up to 30 slots per kind using the policy above.", Callback = function() local freed = 0 for _ = 1, 30 do local f = ensureRoom(state.keepFree) freed = freed + f if f == 0 then break end end notify(freed > 0 and ("freed " .. freed .. " slots") or "nothing sellable", freed > 0 and "good" or "warn") end, }) sSec:Button({ Id = "sell-preset-rare", Text = "Apply preset: rare-pet farm", Style = "Primary", Description = "Hatch Jungle, keep Swift Samurai, 5 duplicates, dump everything below Unique.", Callback = function() state.keepTop = 0 state.keepDupes = 5 state.keepEvolved = true state.sellBelow = "Unique" state.keepNames["Swift Samurai"] = true state.snipePet = "Swift Samurai" state.hatchChoice = "Jungle" state.rankMode = "MARKET" state.autoEvolve = true state.evolvePets, state.evolveTrails = true, true evolveApply() U.keepTop:Set(0, true) U.keepDupes:Set(5, true) U.keepEvolved:Set(true, true) U.floor:Set("Unique", true) U.rankMode:Set("MARKET", true) U.evolveOnlyKept:Set(false, true) U.snipe:Set("Swift Samurai", true) U.crystal:Set("Jungle", true) if state.hatcher then hatchSet(true, "Jungle") end notify("preset: keep+evolve Swift Samurai, dump everything below Unique", "good") setStatus("Preset applied: hatch Jungle, keep Swift Samurai, auto-evolve on", MONO.rare) syncControls() end, }) local lSec = petTab:Section({ Title = "Live policy state" }) U.sellPreview = lSec:Label({ Id = "live-sellpreview", Text = "next to sell: ..." }) U.evolveWatch = lSec:Label({ Id = "live-evolvewatch", Text = "evolve watch: ..." }) U.keepList = lSec:Label({ Id = "live-keeplist", Text = "keep list: none" }) -- ============ AUTO EVOLVE ============ local eSec = petTab:Section({ Title = "Auto evolve", Description = "Five non-evolved copies of the same name become one evolved copy: the server consumes four and leaves one marked evolved.", }) U.evolveAll = eSec:Toggle({ Id = "evolve-all", Text = "Auto evolve everything", Default = state.autoEvolve, Callback = function(on) state.autoEvolve = on if on then state.evolvePets, state.evolveTrails = true, true else state.evolvePets, state.evolveTrails = false, false end evolveApply() syncControls() end, }) U.evolvePets = eSec:Toggle({ Id = "evolve-pets", Text = "Auto evolve pets only", Default = state.evolvePets and not state.autoEvolve, Callback = function(on) state.evolvePets = on evolveApply() end, }) U.evolveTrails = eSec:Toggle({ Id = "evolve-trails", Text = "Auto evolve trails only", Default = state.evolveTrails and not state.autoEvolve, Callback = function(on) state.evolveTrails = on evolveApply() end, }) eSec:Button({ Id = "evolve-now", Text = "Evolve everything ready now", Callback = function() local a, b = evolveNow() notify(string.format("evolved %d pet name(s), %d trail name(s)", #a, #b), (#a + #b) > 0 and "good" or "warn") end, }) eSec:Paragraph({ Title = "Evolve remotes", Content = 'petEvolveEvent:FireServer("evolvePet", name) and evolveTrailEvent:FireServer("evolveTrail", instance). Evolution needs five copies of the same name, so the sell policy keeps duplicates of kept names.', }) -- ============ AUTO EQUIP ============ local qSec = petTab:Section({ Title = "Auto equip" }) U.equipSet = qSec:Toggle({ Id = "equip-auto", Text = "Auto-equip best pets", Default = state.autoEquip, Description = "Fills every empty pet slot with the best pet available.", Callback = function(on) state.autoEquip = on equipApply() end, }) qSec:Button({ Id = "equip-now", Text = "Equip best now", Callback = function() local n = autoEquipStep() notify(n > 0 and ("equipped " .. n .. " pets") or "already optimal", n > 0 and "good" or "info") end, }) qSec:Label({ Text = "Equipping needs a free slot: v5 unequips the weakest equipped pet first, then equips. Equipped pets can never be sold by the sell engine.", }) -- ============ TAB: WORLD LOOPS ============ local worldTab = window:Tab({ Id = "world", Title = "Loops" }) local TEXTURES = { hoop = { Title = "Hoop farm", Text = "Hoop loop (35 hoops per pass, 8.6s cycle)", Description = "Hoops pay out on a per-hoop debounce." }, quest = { Title = "Quests", Text = "Collect completed quests", Description = "Collects anything the quest remote accepts as complete." }, rebirth = { Title = "Rebirth", Text = "Auto rebirth when level is enough", Description = 'rebirthEvent:FireServer("rebirthRequest") once level >= calculateRequiredRebirthLevel(rebirths, player).' }, ultimates = { Title = "Ultimate upgrades", Text = "Auto-buy ultimates", Description = 'ultimatesRemote:InvokeServer("upgradeUltimate", name). Costs rebirths, so it is ordered by payoff: Step Booster, Gem Booster, Ethereal Orbs, Demon Hoops, Infernal Gems, slots, chests, quests, trails, Divine Rebirth, spins.' }, chests = { Title = "Chests", Text = "Open golden / enchanted / magma / jungle chests", Description = 'checkChestRemote:InvokeServer("Golden Chest") and friends. Each has its own 6 hour cooldown.' }, ramps = { Title = "Speed ramps", Text = "Touch speed ramps", Description = "Unverified ramp loop, kept from v4." }, } for _, L in ipairs(SIMPLE_LOOPS) do local meta = TEXTURES[L[1]] local sec = worldTab:Section({ Title = meta.Title, Description = meta.Description }) U[L[1]] = sec:Toggle({ Id = "loop-" .. L[1], Text = meta.Text, Default = state[L[2]], Callback = function(on) simpleApply(L[2], L[3], L[4], on and true or false) end, }) if L[1] == "rebirth" then U.rebirthReserve = sec:Slider({ Id = "rebirth-reserve", Text = "Keep N rebirths spare", Min = 0, Max = 20, Step = 1, Default = state.rebirthReserve, Description = "Stops spending rebirths when the live count is at or under this.", Callback = function(v) state.rebirthReserve = v end, }) end end -- ============ TAB: STATUS ============ local statusTab = window:Tab({ Id = "status", Title = "Status" }) local stSec = statusTab:Section({ Title = "Live status" }) U.badge = stSec:Badge({ Id = "status-tone", Default = "Info", Kind = "Secondary" }) U.status = stSec:Label({ Id = "status-text", Text = "Idle" }) local ssSec = statusTab:Section({ Title = "Session" }) U.session = ssSec:Label({ Id = "session-text", Text = sessionText() }) U.petBar = ssSec:Progress({ Id = "cap-pets", Text = "Pet capacity", Min = 0, Max = 100, Default = 0 }) U.petCap = ssSec:Label({ Id = "cap-pets-text", Text = "pets 0/0" }) U.trailBar = ssSec:Progress({ Id = "cap-trails", Text = "Trail capacity", Min = 0, Max = 100, Default = 0 }) U.trailCap = ssSec:Label({ Id = "cap-trails-text", Text = "trails 0/0" }) local ctlSec = statusTab:Section({ Title = "Controls" }) ctlSec:Button({ Id = "stop-all", Text = "Stop all loops", Style = "Danger", Description = "Stops every farm loop and leaves the window open.", Callback = function() state.orbFarming, state.hoopLoop, state.hatcher, state.quest = false, false, false, false state.evolvePets, state.evolveTrails, state.ramps = false, false, false state.autoRebirth, state.autoUltimates, state.chests, state.autoEquip = false, false, false, false state.autoEvolve = false syncControls() notify("all loops stopping", "warn") end, }) ctlSec:Keybind({ Id = "hotkey-hatch", Text = "Hatch hotkey", Default = Enum.KeyCode.H, Mode = "Toggle", Callback = function(active) hatchSet(active and true or false) syncControls() end, }) -- ============ CONFIGURATION ============ local cfgSec = statusTab:Section({ Title = "Configuration", Description = "Named profiles are stored under ProjectUAI/UI/ by the library. The profile called \"default\" is restored silently when the script starts; everything else is explicit.", }) U.profile = cfgSec:Input({ Id = "cfg-profile", Text = "Profile name", Default = "default", MaxLength = 48, Persist = false, }) U.json = cfgSec:Input({ Id = "cfg-json", Text = "Configuration JSON", MultiLine = true, Lines = 4, MaxLength = 262144, Placeholder = "Export fills this field; Import reads it back.", Persist = false, }) cfgSec:Button({ Id = "cfg-save", Text = "Save profile", ActionText = "Saving", Style = "Primary", Callback = function() local ok, info = window:SaveConfig(U.profile:Get()) if ok then notify("saved " .. tostring(info), "good") else notify("save failed: " .. tostring(info), "bad") end end, }) cfgSec:Button({ Id = "cfg-load", Text = "Load profile", ActionText = "Loading", Description = "Loads the profile into the controls and applies it to the running loops.", Callback = function() local ok, info = window:LoadConfig(U.profile:Get(), { Silent = true }) if ok == false then notify("load failed: " .. tostring(info), "bad") return end applyControls() notify("profile applied", "good") end, }) cfgSec:Button({ Id = "cfg-export", Text = "Export to field", Callback = function() U.json:Set(window:ExportConfig(), true) notify("configuration exported", "info") end, }) cfgSec:Button({ Id = "cfg-copy", Text = "Copy JSON to clipboard", Callback = function() local json = window:ExportConfig() U.json:Set(json, true) if type(setclipboard) == "function" then pcall(setclipboard, json) notify("configuration copied to clipboard", "good") else notify("clipboard unavailable; use the field below", "warn") end end, }) cfgSec:Button({ Id = "cfg-import", Text = "Import from field", Callback = function() local text = tostring(U.json:Get() or "") if text == "" then notify("nothing to import", "warn") return end local ok, info = window:ImportConfig(text, { Silent = true }) if ok then applyControls() notify("imported " .. tostring(info) .. " value(s)", "good") else notify("import failed: " .. tostring(info), "bad") end end, }) cfgSec:Button({ Id = "cfg-reset", Text = "Reset controls to session defaults", Style = "Danger", Callback = function() window:Confirm({ Title = "Reset controls?", Content = "Every control returns to the value it had when this window was created, then the running loops are re-applied.", ConfirmText = "Reset", Danger = true, Callback = function() for _, control in pairs(U) do if type(control) == "table" and type(control.Get) == "function" and type(control.Reset) == "function" then pcall(function() control:Reset(true) end) end end applyControls() notify("controls reset", "info") end, }) end, }) -- keep list: the field is a command box, and it mirrors the live list whenever -- it is empty or already showing the mirror, so a saved profile keeps the names local function keepNameCommit(value) for part in tostring(value or ""):gmatch("[^,]+") do local name = part:gsub("^%s+", ""):gsub("%s+$", "") if name ~= "" then state.keepNames[name] = true end end end if type(U.keepName.OnCommit) == "function" then U.keepName:OnCommit(keepNameCommit) else U.keepName:OnChanged(keepNameCommit) end -- ============ WINDOW CLEANUP ============ local function stopEverything() state.orbFarming, state.hoopLoop, state.hatcher, state.quest = false, false, false, false state.evolvePets, state.evolveTrails, state.ramps = false, false, false state.autoRebirth, state.autoUltimates, state.chests, state.autoEquip = false, false, false, false state.autoEvolve = false end window:OnDestroy(function() stopEverything() for _, key in ipairs({ "orbThread", "hoopThread", "hatchThread", "questThread", "petThread", "trailThread", "evolveThread", "rampThread", "rebirthThread", "ultimateThread", "chestThread", "equipThread" }) do state[key] = nil end end) -- ============ RESTORE SAVED PROFILE ============ do local restored = false pcall(function() local r1, r2 = window:LoadConfig("default", { Silent = true }) if r1 == false then notify("saved profile not restored: " .. tostring(r2), "warn") else restored = r1 ~= nil or r2 ~= nil end end) if restored then applyControls() end end -- ============ PROGRAMMATIC CONTROL (drives the ;los IY plugin) ============ GENV.__LOS_AUTOFARM = { instance = INSTANCE_ID, state = state, crystals = crystals, prices = priceOf, stats = { hatch = hatchStats, sell = sellStats, equip = equipStats }, orbFarm = function(choice) if choice == nil then state.orbFarming = false return end toggleOrb(choice) end, hatch = function(choice) if choice == nil then state.hatcher = false return end toggleHatch(choice) end, hoop = function(on) if on and not state.hoopLoop then state.hoopLoop = true if not state.hoopThread then state.hoopThread = spawnLoop(hoopLoop) end elseif not on then state.hoopLoop = false end end, quests = function(on) if on and not state.quest then state.quest = true if not state.questThread then state.questThread = spawnLoop(questLoop) end elseif not on then state.quest = false end end, evolve = function(which, on) if which == "all" then state.autoEvolve = on ~= false if state.autoEvolve then state.evolvePets, state.evolveTrails = true, true if not state.evolveThread then state.evolveThread = spawnLoop(evolveLoop) end else state.evolvePets, state.evolveTrails = false, false end elseif which == "pets" then state.evolvePets = on ~= false if state.evolvePets and not state.evolveThread then state.evolveThread = spawnLoop(evolveLoop) end elseif which == "trails" then state.evolveTrails = on ~= false if state.evolveTrails and not state.evolveThread then state.evolveThread = spawnLoop(evolveLoop) end end end, evolveNow = evolveNow, evolveReady = function() local out = {} for _, kind in ipairs({ "pets", "trails" }) do for name, n in pairs(evolveCounts(kind)) do if n >= 2 then table.insert(out, string.format("%s %s %d/5", kind, name, n)) end end end table.sort(out) return out end, sellPreview = function(kind, n) return sellPreview(kind or "pets", n or 5) end, rank = function(mode) if mode then state.rankMode = mode end return state.rankMode end, sellFloor = function(r) if r then state.sellBelow = r end return state.sellBelow end, keepDupes = function(n) if n then state.keepDupes = n end return state.keepDupes end, ramps = function(on) if on and not state.ramps then state.ramps = true if not state.rampThread then state.rampThread = spawnLoop(rampLoop) end elseif not on then state.ramps = false end end, sellJunk = function(n) local freed = 0 for _ = 1, n or 20 do local f = ensureRoom(state.keepFree) freed = freed + f if f == 0 then break end end return freed end, sellAll = function() local n = 0 state.keepTop, state.keepDupes = 0, 0 for _ = 1, 80 do if not sellLowest("pets") then break end n = n + 1 task.wait(0.1) end for _ = 1, 80 do if not sellLowest("trails") then break end n = n + 1 task.wait(0.1) end return n end, autoEquip = function(on) state.autoEquip = on and true or false if state.autoEquip and not state.equipThread then state.equipThread = spawnLoop(equipLoop) end end, equipNow = function() return autoEquipStep() end, rebirth = function(on) state.autoRebirth = on and true or false if state.autoRebirth and not state.rebirthThread then state.rebirthThread = spawnLoop(rebirthLoop) end end, ultimates = function(on) state.autoUltimates = on and true or false if state.autoUltimates and not state.ultimateThread then state.ultimateThread = spawnLoop(ultimateLoop) end end, chests = function(on) state.chests = on and true or false if state.chests and not state.chestThread then state.chestThread = spawnLoop(chestLoop) end end, snipe = function(petName, crystal) state.snipePet = petName or "" if crystal then local c = crystalByName(crystal) state.hatchChoice = c and c.base or crystal end if state.snipePet ~= "" then toggleHatch(state.hatchChoice) end local base = crystalByName(state.hatchChoice) and crystalByName(state.hatchChoice).base or "Jungle" return oddsFor(base, state.snipePet) end, keep = function(petName, on) if petName == nil then return state.keepNames end state.keepNames[petName] = on ~= false or nil return state.keepNames end, oneHatch = function(crystal) local saved = state.hatchChoice if crystal then state.hatchChoice = crystal end local ok, err, name, rarity = hatchOnce() state.hatchChoice = saved return ok, err, name, rarity end, collectQuests = collectQuests, stopAll = function() state.orbFarming, state.hoopLoop, state.hatcher, state.quest = false, false, false, false state.evolvePets, state.evolveTrails, state.ramps = false, false, false state.autoRebirth, state.autoUltimates, state.chests, state.autoEquip = false, false, false, false state.autoEvolve = false end, } -- ============ REFRESH LOOP (UI LIB EDITION) ============ do local acc, accPill = 0, 0 local conn, warned = nil, false conn = RunService.Heartbeat:Connect(function(dt) if not isCurrent() then conn:Disconnect() return end local ok, err = pcall(function() accPill = accPill + dt if accPill >= 0.2 then accPill = 0 syncControls() end acc = acc + dt if acc < 1 then return end acc = 0 if U.session then U.session:SetText(sessionText()) end local pd = sellPreview("pets", 3) local td = sellPreview("trails", 2) if U.sellPreview then U.sellPreview:SetText("next pets to sell: " .. (#pd > 0 and table.concat(pd, ", ") or "nothing") .. "\nnext trails: " .. (#td > 0 and table.concat(td, ", ") or "nothing")) end local watch = {} for _, kind in ipairs({ "pets", "trails" }) do for name, n in pairs(evolveCounts(kind)) do if n >= 2 then table.insert(watch, name .. " " .. n .. "/5") end end end table.sort(watch) if U.evolveWatch then U.evolveWatch:SetText("evolve watch (non-evolved copies): " .. (#watch > 0 and table.concat(watch, ", ") or "nothing yet") .. (state.autoEvolve and " [auto ON]" or " [auto off]")) end if U.keepList then local keeps = {} for name in pairs(state.keepNames) do table.insert(keeps, name) end table.sort(keeps) U.keepList:SetText("keep list: " .. (#keeps > 0 and table.concat(keeps, ", ") or "none") .. " ranking: " .. state.rankMode .. " floor: " .. state.sellBelow) end local c = cap() local pn, tn = itemTotal("pets"), itemTotal("trails") if U.petBar then U.petBar:Set(c > 0 and math.min(pn / c, 1) * 100 or 0) end if U.petCap then U.petCap:SetText(string.format("pets %d/%d (%d free)", pn, c, math.max(c - pn, 0))) end if U.trailBar then U.trailBar:Set(c > 0 and math.min(tn / c, 1) * 100 or 0) end if U.trailCap then U.trailCap:SetText(string.format("trails %d/%d (%d free)", tn, c, math.max(c - tn, 0))) end end) if not ok and not warned then warned = true warn("[LOS Autofarm v5] refresh error: " .. tostring(err)) end end) window:Give(conn) end print("[LOS Autofarm v5 | UI LIB] loaded. Window Id 'los-autofarm-v5'. " .. "Hatch = openCrystalRemote:InvokeServer(\"openCrystal\", \" Crystal\"). " .. #crystals .. " crystals, " .. (function() local n = 0 for _ in pairs(priceOf) do n = n + 1 end return n end)() .. " priced pets/trails.")