18 13 No KeySteal An Exu Script (Delta Mobile) – Kryven Hub: Auto Steal, Pen Sell, Anti AFK
Steal An Exu · RobloxDescription
Kryven Hub is a Steal An Exu script you can run in Delta on Android, iOS or PC. It includes auto steal, area priority, pen full: sell and more. It shows live counters for stolen, placed, kept and sold Exus. Created by Navaaa. No key.
Key Features
- Auto Steal: teleports to Exus, steals and returns to your pen
- Pick areas and priority, best area first
- Pen-full policy: Keep, Sell or Stop
- Escape on steal
- Teleport or Walk movement
- WalkSpeed and JumpPower
- Infinite Jump
- Anti AFK
- Teleport to pen
- Save and load configs
Steal An Exu is a Brazilian steal-and-collect game, currently in its Exu-lloween update. You train on the treadmill to get faster, grab rarer and rarer Exus while dodging the Guardians, keep them in your pen to earn money, and fuse three identical Exus into a bigger one. Kryven Hub 3.1 by Navaaa is a Steal An Exu script that automates the stealing and gives you a status panel that shows what it’s doing.
Farm tab
| Section | Controls |
|---|---|
| Auto Steal | Auto Steal; Return and place (on); If the pen is full: Keep in inventory, Sell or Stop; Escape on steal (on); Batch 1–5 |
| Movement | Go to Exu and Return to pen, each Teleport or Walk; Steal method Instant or Hold; Teleport settle 0–400 ms (120 by default) |
| Targeting | Priority: Efficiency, Highest Need or Nearest; Area order: best area first, nearest first or off; Max range up to “any” |
| Areas | A row for each area, quick-select All or Last, and “Forget spawn points” for when the map changes |
| Status | Current state, last target, and stolen / placed / kept / sold counters |
The Player tab has WalkSpeed (8–200), JumpPower, Infinite Jump and Anti AFK (on by default). Teleport has a button to go straight to your pen and one for each area.
Running it
No key, no loader. The entire hub, over 2,600 lines, is in the code box, and it doesn’t download anything. Paste it into Delta (Fluxus, Codex, Arceus X and desktop executors also made the page’s tested row) after you’ve joined. RightShift opens and closes the window, and you can change that key under Settings, along with UI scale (60–140%), background blur and pop-up notifications. Your setup is saved to a kryven_hub folder for each place and loads automatically next time unless you turn that off. “Unload hub” in Settings stops everything and removes the window.
FAQ
What happens when my pen is full? That depends on the dropdown. Keep puts new Exus in your inventory, Stop ends the run, and Sell sells them. The menu warns that selling is permanent.
Teleport or Walk? Teleport is quicker. Walk takes the normal route, which looks more natural if other players are watching. You can set each leg separately, so you could walk to the Exu and teleport home, for example.
Does it fuse Exus? No. Fusing three of the same Exu is still something you do yourself.
Pen tip: set “If the pen is full” to Keep in inventory while you’re still learning which Exus matter. A sale can’t be undone, and the one you sold might have been the third copy you needed for a fusion.
--!nocheck
-- kryven_hub.lua: Kryven Hub for Steal An Exu (auto steal core + hub UI).
--
-- Part 1 (Core): pure Lua, no Roblox API. Runs under plain Lua/Luau for tests.
-- Parts 2-4 (Roblox adapters, UI toolkit, tabs): only run when `game` exists.
--
-- Contracts consumed by the core:
-- ExuSnapshot { key:any, name:string, rarity:string?, need:number, pos:{x,y,z}, reach:number, enabled:boolean }
-- source.snapshot() -> list | nil, err source.playerPos() -> {x,y,z} | nil
-- source.basePos() -> {x,y,z} | nil (needed when cfg.deposit)
-- mover.approach(pos, within, purpose) -> boolean purpose = "target" | "base" | "escape"; true only when within `within`
-- actor.steal(snapshot, onTriggered) -> ok, item (calls onTriggered() the moment the prompt is triggered, before
-- the Tool is confirmed; ok only when acquisition was confirmed;
-- item = { id = ExuId })
-- placer.place(id) -> ok, detail? (ok only when the placement was confirmed; needed when cfg.deposit)
-- clock.now() -> seconds
-- optional: source.stillValid(snapshot) -> boolean (false = the target no longer exists, e.g. after a reset)
-- optional: placer.holds(id) -> boolean (false = the Exu is no longer in the player's hands)
-- optional: control.running() -> boolean (false = the user switched the feature off; interruptions
-- caused by that are not counted as failures)
--
-- Areas: the game's own zone definitions have not been read yet (UNKNOWN). Areas are INFERRED from the spawn
-- points of Runtime Exus: points closer than cfg.areaGap are one area (single linkage on x/z), areas are ordered by
-- centroid distance from cfg.areaOrigin and the farthest one is "last". Observed basis: spawn rows sit ~24-84 studs
-- apart inside a zone and >=290 studs apart between zones (z 150 / 442-450 / 802-810); pens sit within ~30 studs of
-- the world origin. Spawn points are remembered, so an area stays listed after its Exus are stolen.
local Core = {}
local function isFinite(n)
return type(n) == "number" and n == n and n > -math.huge and n < math.huge
end
local function isVec(p)
return type(p) == "table" and isFinite(p.x) and isFinite(p.y) and isFinite(p.z)
end
function Core.distance(a, b)
local dx, dy, dz = a.x - b.x, a.y - b.y, a.z - b.z
return math.sqrt(dx * dx + dy * dy + dz * dz)
end
function Core.pointKey(pos)
return string.format("%d:%d", math.floor(pos.x + 0.5), math.floor(pos.z + 0.5))
end
function Core.defaultConfig()
return {
standoff = 1, -- studs kept inside the prompt's MaxActivationDistance
blockSeconds = 20, -- how long a failed target is skipped
maxConsecutiveFailures = 5, -- stop reason "too_many_failures" after this many
deposit = true, -- carry stolen Exus to the own pen and place them
batch = 1, -- steal this many before depositing (hand capacity is UNKNOWN)
baseRadius = 6, -- studs from the pen's Spawn part that count as "at base"
returnInstant = false, -- set by the integration when the base leg is a teleport (drops it from the estimate)
escapeOnTrigger = false, -- go to the base the moment the prompt is triggered, before the Tool is confirmed
recoverSeconds = 8, -- pause length after a transient halt before trying again
maxHalts = 0, -- transient halts tolerated without any success before a hard stop (0 = stop at once)
onPlaceFail = "stop", -- when the pen refuses an Exu: "stop" (halt) | "keep" (leave it in the inventory) | "sell"
placeFailLimit = 2, -- refusals in a row before placing is skipped for noPlaceSeconds
noPlaceSeconds = 60, -- how long placing is skipped after placeFailLimit refusals (the trip home still happens)
areaPriority = false, -- "best": highest area index first | "near": lowest first | false: ignore areas when ranking
stopOnUnknownId = true, -- stop when a stolen Exu has no id (false: leave it in the inventory and go on)
areaGap = 120, -- studs: spawn points closer than this belong to the same area
areaOrigin = { x = 0, y = 0, z = 0 },
select = {
order = "efficiency", -- "efficiency": Need per second of trip | "value": Need desc | "nearest"
speed = nil, -- studs/s, supplied per cycle by the integration; invalid -> "nearest"
fixedSeconds = 1.5, -- per-steal overhead in the efficiency estimate
maxRange = math.huge, -- studs from the player
minNeed = -math.huge,
maxNeed = math.huge,
rarities = nil, -- set { [rarity] = true } or nil for any
},
}
end
local function mergeConfig(user)
local cfg = Core.defaultConfig()
if user then
for k, v in pairs(user) do
if k ~= "select" then
cfg[k] = v
end
end
if user.select then
for k, v in pairs(user.select) do
cfg.select[k] = v
end
end
end
return cfg
end
-- Returns ok, reason. Rejects anything that would turn missing data into a believable value.
function Core.validate(s)
if type(s) ~= "table" then
return false, "not_a_table"
end
if s.key == nil then
return false, "no_key"
end
if not isFinite(s.need) then
return false, "bad_need"
end
if not isVec(s.pos) then
return false, "bad_pos"
end
if not isFinite(s.reach) or s.reach <= 0 then
return false, "bad_reach"
end
if s.enabled ~= true then
return false, "disabled"
end
return true
end
-- Returns best snapshot (or nil) and stats { total, rejected = {reason=count}, filtered, orderUsed }.
-- `base` (optional vec) adds the target->base leg to the efficiency trip estimate.
-- `areaFilter` (optional) is called with the snapshot and returns whether its area is allowed.
function Core.select(snaps, me, selectCfg, isBlocked, base, areaFilter, areaRank)
local order = selectCfg.order
local speedOk = isFinite(selectCfg.speed) and selectCfg.speed > 0
if order == "efficiency" and not speedOk then
order = "nearest"
end
local stats = { total = #snaps, rejected = {}, filtered = 0, orderUsed = order }
local best, bestNeed, bestDist, bestScore, bestRank
for _, s in ipairs(snaps) do
local ok, why = Core.validate(s)
if not ok then
stats.rejected[why] = (stats.rejected[why] or 0) + 1
else
local d = Core.distance(me, s.pos)
local pass = d <= selectCfg.maxRange
and s.need >= selectCfg.minNeed
and s.need <= selectCfg.maxNeed
and (selectCfg.rarities == nil or (s.rarity ~= nil and selectCfg.rarities[s.rarity] == true))
and not (isBlocked and isBlocked(s.key))
and (areaFilter == nil or areaFilter(s))
if not pass then
stats.filtered = stats.filtered + 1
else
local score
if order == "efficiency" then
local trip = d + (base and Core.distance(s.pos, base) or 0)
local cost = math.max(trip / selectCfg.speed + (selectCfg.fixedSeconds or 0), 1e-6)
score = s.need / cost
end
local rank = areaRank and areaRank(s) or 0
local better
if best == nil then
better = true
elseif rank ~= bestRank then
better = rank > bestRank -- the preferred area wins before any per-target ranking
elseif order == "efficiency" then
better = score > bestScore or (score == bestScore and d < bestDist)
elseif order == "nearest" then
better = d < bestDist or (d == bestDist and s.need > bestNeed)
else
better = s.need > bestNeed or (s.need == bestNeed and d < bestDist)
end
if better then
best, bestNeed, bestDist, bestScore, bestRank = s, s.need, d, score, rank
end
end
end
end
return best, stats
end
-- Single-linkage clustering of spawn points on the x/z plane.
-- points: { [key] = {x,y,z} }. Returns { list = { {index, count, centroid, dist} ... ordered by dist asc },
-- byKey = { [pointKey] = index } }.
function Core.buildAreas(points, gap, origin)
local pts = {}
for k, p in pairs(points) do
pts[#pts + 1] = { x = p.x, y = p.y, z = p.z, key = k }
end
table.sort(pts, function(a, b)
if a.x ~= b.x then
return a.x < b.x
end
return a.z < b.z
end)
local parent = {}
for i = 1, #pts do
parent[i] = i
end
local function find(i)
while parent[i] ~= i do
parent[i] = parent[parent[i]]
i = parent[i]
end
return i
end
for i = 1, #pts do
for j = i + 1, #pts do
local dx, dz = pts[i].x - pts[j].x, pts[i].z - pts[j].z
if math.sqrt(dx * dx + dz * dz) <= gap then
parent[find(i)] = find(j)
end
end
end
local groups, order = {}, {}
for i = 1, #pts do
local r = find(i)
local g = groups[r]
if not g then
g = { count = 0, sx = 0, sy = 0, sz = 0, keys = {} }
groups[r] = g
order[#order + 1] = g
end
g.count = g.count + 1
g.sx, g.sy, g.sz = g.sx + pts[i].x, g.sy + pts[i].y, g.sz + pts[i].z
g.keys[#g.keys + 1] = pts[i].key
end
local list = {}
for _, g in ipairs(order) do
local c = { x = g.sx / g.count, y = g.sy / g.count, z = g.sz / g.count }
local dx, dz = c.x - origin.x, c.z - origin.z
list[#list + 1] = { count = g.count, centroid = c, dist = math.sqrt(dx * dx + dz * dz), keys = g.keys,
alive = 0, rarities = {} }
end
table.sort(list, function(a, b)
if a.dist ~= b.dist then
return a.dist < b.dist
end
return a.centroid.x < b.centroid.x
end)
local byKey = {}
for i, a in ipairs(list) do
a.index = i
for _, k in ipairs(a.keys) do
byKey[k] = i
end
end
return { list = list, byKey = byKey }
end
-- Remembers every spawn point seen, rebuilds the areas when a new one appears and refreshes alive counts and
-- the two most common rarities per area from the current snapshot.
function Core.observe(ctl, snaps)
local dirty = ctl.areas == nil
for _, s in ipairs(snaps) do
if type(s) == "table" and isVec(s.pos) then
local k = Core.pointKey(s.pos)
if not ctl.points[k] then
ctl.points[k] = { x = s.pos.x, y = s.pos.y, z = s.pos.z }
dirty = true
end
end
end
if dirty then
ctl.areas = Core.buildAreas(ctl.points, ctl.cfg.areaGap, ctl.cfg.areaOrigin)
end
local counts = {}
for _, a in ipairs(ctl.areas.list) do
a.alive = 0
counts[a.index] = {}
end
for _, s in ipairs(snaps) do
if type(s) == "table" and isVec(s.pos) then
local idx = ctl.areas.byKey[Core.pointKey(s.pos)]
if idx then
ctl.areas.list[idx].alive = ctl.areas.list[idx].alive + 1
if type(s.rarity) == "string" then
counts[idx][s.rarity] = (counts[idx][s.rarity] or 0) + 1
end
end
end
end
for _, a in ipairs(ctl.areas.list) do
local names = {}
for name in pairs(counts[a.index]) do
names[#names + 1] = name
end
table.sort(names, function(x, y)
local cx, cy = counts[a.index][x], counts[a.index][y]
if cx ~= cy then
return cx > cy
end
return x < y
end)
a.rarities = { names[1], names[2] }
end
return ctl.areas
end
-- Area selection: mode "all" | "last" (always the farthest known area) | "custom" (explicit index set).
function Core.selectAll(ctl)
ctl.areaSelect = { mode = "all", set = {} }
end
function Core.selectLast(ctl)
ctl.areaSelect = { mode = "last", set = {} }
end
function Core.isAreaSelected(ctl, idx)
local sel = ctl.areaSelect
if sel.mode == "all" then
return true
elseif sel.mode == "last" then
return ctl.areas ~= nil and idx == #ctl.areas.list
end
return sel.set[idx] == true
end
function Core.toggleArea(ctl, idx)
local sel = ctl.areaSelect
if sel.mode == "all" then
ctl.areaSelect = { mode = "custom", set = { [idx] = true } }
return
end
if sel.mode == "last" then
local set = {}
if ctl.areas and #ctl.areas.list > 0 then
set[#ctl.areas.list] = true
end
sel = { mode = "custom", set = set }
ctl.areaSelect = sel
end
sel.set[idx] = (not sel.set[idx]) or nil
end
local function areaAllowed(ctl, s)
if ctl.areaSelect.mode == "all" then
return true
end
local idx = ctl.areas and ctl.areas.byKey[Core.pointKey(s.pos)]
return idx ~= nil and Core.isAreaSelected(ctl, idx)
end
function Core.newController(adapters, userCfg)
return {
a = adapters,
cfg = mergeConfig(userCfg),
blocked = {},
carried = {}, -- ExuIds stolen and not yet placed
points = {}, -- remembered spawn points
areas = nil,
areaSelect = { mode = "all", set = {} },
failures = 0,
stolen = 0,
placed = 0,
lost = 0, -- carried Exus that disappeared (reset/respawn) before they were placed
kept = 0, -- Exus left in the inventory because the pen refused them (or no base was found)
sold = 0,
placeFails = 0,
noPlaceUntil = nil,
haltCount = 0, -- transient halts since the last success
lastHalt = nil,
pausedUntil = nil,
stopReason = nil,
}
end
-- Clears every stop/pause state; used when the user switches the feature back on.
function Core.reset(ctl)
ctl.stopReason, ctl.pausedUntil, ctl.lastHalt = nil, nil, nil
ctl.failures, ctl.haltCount = 0, 0
ctl.placeFails, ctl.noPlaceUntil = 0, nil
end
-- Forgets the remembered spawn points (e.g. after the map changed); areas are rebuilt on the next observe.
function Core.clearAreas(ctl)
ctl.points, ctl.areas = {}, nil
end
local function isBlocked(ctl, key)
local untilT = ctl.blocked[key]
if not untilT then
return false
end
if ctl.a.clock.now() >= untilT then
ctl.blocked[key] = nil
return false
end
return true
end
-- Transient problems (night reset, respawn, pen not ready) pause the controller and retry; only after
-- cfg.maxHalts halts without a success does it stop for good.
local function halt(ctl, reason)
ctl.lastHalt = reason
ctl.haltCount = ctl.haltCount + 1
if ctl.haltCount > ctl.cfg.maxHalts then
ctl.stopReason = reason
else
ctl.pausedUntil = ctl.a.clock.now() + ctl.cfg.recoverSeconds
ctl.failures = 0
end
end
local function bump(ctl)
ctl.failures = ctl.failures + 1
if ctl.failures >= ctl.cfg.maxConsecutiveFailures then
halt(ctl, "too_many_failures")
end
end
local function stillValid(ctl, target)
local check = ctl.a.source.stillValid
return check == nil or check(target) ~= false
end
-- True when the integration says the user switched the feature off; such interruptions are not failures.
local function aborted(ctl)
local control = ctl.a.control
return control ~= nil and control.running() == false
end
local function fail(ctl, target, result)
ctl.blocked[target.key] = ctl.a.clock.now() + ctl.cfg.blockSeconds
bump(ctl)
return result, target
end
-- Goes to the own pen and places every carried Exu. Results:
-- "deposited" {placed} | "base_unavailable" (stops) | "base_approach_failed" (retried next cycle)
-- "place_failed" {placed, detail} (stops; carried ids are kept)
function Core.deposit(ctl)
local cfg = ctl.cfg
local base = ctl.a.source.basePos()
if not isVec(base) then
if cfg.onPlaceFail == "keep" then
local n = #ctl.carried
ctl.kept = ctl.kept + n
ctl.carried = {}
return "base_unavailable", { kept = n }
end
halt(ctl, "base_unavailable")
return "base_unavailable"
end
if not ctl.a.mover.approach(base, cfg.baseRadius, "base") then
if aborted(ctl) then
return "aborted"
end
if not isVec(ctl.a.source.playerPos()) then
return "player_unavailable"
end
bump(ctl)
return "base_approach_failed"
end
local placeOn = not (ctl.noPlaceUntil and ctl.a.clock.now() < ctl.noPlaceUntil)
local placed, lostNow, keptNow, soldNow, lastDetail = 0, 0, 0, 0, nil
while #ctl.carried > 0 do
local id = ctl.carried[1]
if not placeOn then
table.remove(ctl.carried, 1)
ctl.kept = ctl.kept + 1
keptNow = keptNow + 1
else
local ok, detail = ctl.a.placer.place(id)
if ok then
table.remove(ctl.carried, 1)
placed = placed + 1
ctl.placed = ctl.placed + 1
ctl.placeFails = 0
elseif ctl.a.placer.holds and ctl.a.placer.holds(id) == false then
table.remove(ctl.carried, 1) -- the Exu vanished with a reset or respawn; nothing left to place
lostNow = lostNow + 1
ctl.lost = ctl.lost + 1
else
lastDetail = detail
ctl.placeFails = ctl.placeFails + 1
if ctl.placeFails >= cfg.placeFailLimit then
ctl.noPlaceUntil = ctl.a.clock.now() + cfg.noPlaceSeconds
ctl.placeFails = 0
placeOn = false
end
if cfg.onPlaceFail == "stop" then
halt(ctl, "place_failed")
return "place_failed", { placed = placed, detail = detail }
end
local sold = false
if cfg.onPlaceFail == "sell" and ctl.a.placer.sell then
sold = ctl.a.placer.sell(id) == true
end
table.remove(ctl.carried, 1)
if sold then
ctl.sold = ctl.sold + 1
soldNow = soldNow + 1
else
ctl.kept = ctl.kept + 1
keptNow = keptNow + 1
end
end
end
end
ctl.failures = 0
if placed > 0 or keptNow > 0 or soldNow > 0 then
ctl.haltCount = 0
end
return "deposited", { placed = placed, lost = lostNow, kept = keptNow, sold = soldNow, detail = lastDetail }
end
-- One decision + action cycle. Results:
-- "stolen" | "steal_failed" | "approach_failed" (target as 2nd value) | "unknown_item_id" (stops)
-- everything returned by Core.deposit
-- "no_target" (stats) | "source_unavailable" (err) | "player_unavailable" | "stopped"
function Core.runOnce(ctl)
if ctl.stopReason then
return "stopped"
end
if ctl.pausedUntil then
local left = ctl.pausedUntil - ctl.a.clock.now()
if left > 0 then
return "paused", { reason = ctl.lastHalt, remaining = left }
end
ctl.pausedUntil = nil
end
local cfg = ctl.cfg
local now = ctl.a.clock.now()
for key, untilT in pairs(ctl.blocked) do
if now >= untilT then
ctl.blocked[key] = nil
end
end
if cfg.deposit and #ctl.carried >= cfg.batch then
return Core.deposit(ctl)
end
local snaps, err = ctl.a.source.snapshot()
if not snaps then
return "source_unavailable", err
end
Core.observe(ctl, snaps)
local me = ctl.a.source.playerPos()
if not isVec(me) then
return "player_unavailable"
end
local base
if cfg.deposit and cfg.batch == 1 and not cfg.returnInstant then
base = ctl.a.source.basePos()
if not isVec(base) then
base = nil
end
end
local areaRank
if cfg.areaPriority == "best" or cfg.areaPriority == "near" then
local sign = cfg.areaPriority == "best" and 1 or -1
areaRank = function(s)
local idx = ctl.areas and ctl.areas.byKey[Core.pointKey(s.pos)]
return idx and idx * sign or -math.huge
end
end
local target, stats = Core.select(snaps, me, cfg.select, function(key)
return isBlocked(ctl, key)
end, base, function(s)
return areaAllowed(ctl, s)
end, areaRank)
if not target then
if cfg.deposit and #ctl.carried > 0 then
return Core.deposit(ctl)
end
return "no_target", stats
end
local within = math.max(1, target.reach - cfg.standoff)
if not ctl.a.mover.approach(target.pos, within, "target") then
if aborted(ctl) then
return "aborted"
end
if not isVec(ctl.a.source.playerPos()) then
return "player_unavailable"
end
if not stillValid(ctl, target) then
return "target_gone", target
end
return fail(ctl, target, "approach_failed")
end
local escapeBase
if cfg.deposit and cfg.escapeOnTrigger then
escapeBase = ctl.a.source.basePos()
if not isVec(escapeBase) then
escapeBase = nil
end
end
local function onTriggered()
if escapeBase then
ctl.a.mover.approach(escapeBase, cfg.baseRadius, "escape")
end
end
local ok, item = ctl.a.actor.steal(target, onTriggered)
if not ok then
if aborted(ctl) then
return "aborted"
end
if not stillValid(ctl, target) then
return "target_gone", target
end
return fail(ctl, target, "steal_failed")
end
ctl.stolen = ctl.stolen + 1
ctl.failures = 0
ctl.haltCount = 0
if cfg.deposit then
if type(item) ~= "table" or item.id == nil then
if cfg.stopOnUnknownId then
ctl.stopReason = "unknown_item_id"
return "unknown_item_id", target
end
return "stolen", target -- cannot be placed without an id; it stays in the inventory
end
ctl.carried[#ctl.carried + 1] = item.id
if #ctl.carried >= cfg.batch then
return Core.deposit(ctl)
end
end
return "stolen", target
end
if game == nil then
return Core
end
-- ===================================================================================================
-- Part 2: Roblox services, shared state and target adapters
-- ===================================================================================================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local Lighting = game:GetService("Lighting")
local Stats = game:GetService("Stats")
local MarketplaceService = game:GetService("MarketplaceService")
local lp = Players.LocalPlayer
local VERSION = "3.1"
local HUB_NAME = "KryvenHub"
local env = (type(getgenv) == "function" and getgenv()) or _G
-- a second execution replaces the first instance
if type(env.__KryvenUnload) == "function" then
pcall(env.__KryvenUnload)
end
local State = { alive = true, enabled = false }
local connections = {}
local function bind(signal, fn)
local c = signal:Connect(fn)
connections[#connections + 1] = c
return c
end
local function safe(fn, ...)
local ok, err = pcall(fn, ...)
if not ok then
warn("[kryven] " .. tostring(err))
end
return ok
end
local CFG = {
moveTarget = "teleport", -- how to reach an Exu: "teleport" (CFrame) | "walk" (Humanoid:MoveTo)
moveBase = "teleport", -- how to return to the pen after a steal: "teleport" | "walk"
settleSeconds = 0.12, -- pause after a teleport so the server sees the new position before the next action
hold = "fire", -- "fire" = fireproximityprompt, falls back to a real hold | "hold" = real hold only
approachTimeout = 20, -- seconds to reach a point before it counts as a failure
holdPadding = 0.05, -- extra seconds on top of the prompt's HoldDuration
fireConfirm = 0.8, -- seconds to see the Tool after fireproximityprompt before falling back to a hold
confirmTimeout = 3, -- seconds to confirm a real hold / a placement
invokeTimeout = 5, -- seconds to wait for the server to answer a placement call
loopDelay = 0,
idleDelay = 1,
teleportSpeed = 500, -- speed fed to the efficiency estimate while teleporting to Exus
core = {
standoff = 1,
blockSeconds = 20,
maxConsecutiveFailures = 5,
deposit = true,
batch = 1,
baseRadius = 6,
areaGap = 120,
escapeOnTrigger = true,
recoverSeconds = 5, -- pause after a transient halt (night reset, respawn) before trying again
maxHalts = math.huge, -- never switch off on its own; only the user's toggle stops the farm
onPlaceFail = "keep", -- a refused placement (pen full) must not stop the farm
areaPriority = "best", -- best selected area first, then the next one when it has no Exus left
stopOnUnknownId = false,
select = { order = "efficiency", maxRange = math.huge, fixedSeconds = 1.5 },
},
}
local fireUnreliable = false -- set after two instant triggers in a row produced no Tool; real holds are used afterwards
local fireMisses = 0
local timeline = { tTrigger = 0, tTool = 0, tTp = 0, tPlaced = 0 } -- os.clock() stamps of the current steal, 0 = not reached
local function toVec(v)
return { x = v.X, y = v.Y, z = v.Z }
end
local function character()
local char = lp.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
local hrp = char and char:FindFirstChild("HumanoidRootPart")
if hum and hrp and hrp:IsA("BasePart") and hum.Health > 0 then
return char, hum, hrp
end
return nil
end
local function promptOf(model)
local root = model:FindFirstChild("Root")
if not (root and root:IsA("BasePart")) then
return nil, nil
end
local prompt = root:FindFirstChildOfClass("ProximityPrompt")
if prompt and prompt.ActionText == "Roubar" then
return prompt, root
end
return nil, nil
end
local function running()
return State.alive and State.enabled
end
-- ExuIds of every Tool the player currently holds (Backpack + Character)
local function toolIds()
local set = {}
local function scan(container)
if container then
for _, t in ipairs(container:GetChildren()) do
if t:IsA("Tool") then
local id = t:GetAttribute("ExuId")
if id ~= nil then
set[id] = true
end
end
end
end
end
scan(lp:FindFirstChild("Backpack"))
scan(lp.Character)
return set
end
local Source = {}
function Source.snapshot()
local runtime = Workspace:FindFirstChild("Runtime")
if not runtime then
return nil, "Workspace.Runtime missing"
end
local out = {}
for _, m in ipairs(runtime:GetChildren()) do
if m:IsA("Model") then
local prompt, root = promptOf(m)
local need = m:GetAttribute("Need")
if prompt and typeof(need) == "number" then
out[#out + 1] = {
key = m,
name = prompt.ObjectText,
rarity = m:GetAttribute("Rarity"),
need = need,
pos = toVec(root.Position),
reach = prompt.MaxActivationDistance,
enabled = prompt.Enabled,
}
end
end
end
return out
end
function Source.playerPos()
local _, _, hrp = character()
return hrp and toVec(hrp.Position) or nil
end
-- A reset or another player's steal removes the model or disables its prompt.
function Source.stillValid(snap)
local model = snap.key
if not (model and model.Parent) then
return false
end
local prompt = promptOf(model)
return prompt ~= nil and prompt.Enabled
end
-- Own pen: Owner attribute == UserId first, then the player's PenName attribute.
local function ownPen()
local map = Workspace:FindFirstChild("Map")
local pens = map and map:FindFirstChild("Pens")
if not pens then
return nil
end
for _, p in ipairs(pens:GetChildren()) do
if p:GetAttribute("Owner") == lp.UserId then
return p
end
end
local name = lp:GetAttribute("PenName")
if typeof(name) == "string" then
return pens:FindFirstChild(name)
end
return nil
end
function Source.basePos()
local pen = ownPen()
local spawn = pen and pen:FindFirstChild("Spawn")
if spawn and spawn:IsA("BasePart") then
return toVec(spawn.Position)
end
return nil
end
local Mover = {}
function Mover.approach(pos, within, purpose)
local mode
if purpose == "escape" then
mode = "teleport" -- the whole point of escaping is speed
elseif purpose == "base" then
mode = CFG.moveBase
else
mode = CFG.moveTarget
end
local target = Vector3.new(pos.x, pos.y, pos.z)
local deadline = os.clock() + CFG.approachTimeout
local lastMove = 0
while running() and os.clock() < deadline do
local _, hum, hrp = character()
if not hrp then
return false
end
if (hrp.Position - target).Magnitude <= within then
return true
end
if mode == "teleport" then
if purpose ~= "target" and timeline.tTp == 0 then
timeline.tTp = os.clock()
end
-- offset magnitude is at most 0.67*within, so the next distance check passes
hrp.CFrame = CFrame.new(target + Vector3.new(0, math.min(3, within * 0.6), within * 0.3))
hrp.AssemblyLinearVelocity = Vector3.zero
task.wait(CFG.settleSeconds)
elseif os.clock() - lastMove > 0.25 then
hum:MoveTo(target)
lastMove = os.clock()
task.wait()
else
task.wait()
end
end
return false
end
local Actor = {}
function Actor.steal(snap, onTriggered)
local model = snap.key
local prompt = model.Parent and promptOf(model)
if not (prompt and prompt.Enabled) then
return false
end
timeline.tTrigger, timeline.tTool, timeline.tTp, timeline.tPlaced = 0, 0, 0, 0
local known = toolIds()
local newId
local conns = {}
local function onChild(child)
if child:IsA("Tool") then
local id = child:GetAttribute("ExuId")
if id ~= nil and not known[id] then
newId = id
timeline.tTool = os.clock()
end
end
end
local backpack = lp:FindFirstChild("Backpack")
if backpack then
conns[#conns + 1] = backpack.ChildAdded:Connect(onChild)
end
if lp.Character then
conns[#conns + 1] = lp.Character.ChildAdded:Connect(onChild)
end
local useFire = CFG.hold == "fire" and type(fireproximityprompt) == "function" and not fireUnreliable
local modes = useFire and { "fire", "hold" } or { "hold" }
local escaped = false
for _, mode in ipairs(modes) do
if mode == "fire" then
pcall(fireproximityprompt, prompt)
else
pcall(function() prompt:InputHoldBegin() end)
task.wait(prompt.HoldDuration + CFG.holdPadding)
pcall(function() prompt:InputHoldEnd() end)
end
timeline.tTrigger = os.clock()
if onTriggered and not escaped then
escaped = true
onTriggered() -- leave before the server answers; Tool detection keeps running on its own connection
end
local deadline = os.clock() + (mode == "fire" and CFG.fireConfirm or CFG.confirmTimeout)
while newId == nil and os.clock() < deadline and running() do
task.wait()
end
if newId ~= nil or escaped or not (model.Parent and prompt.Parent) then
if newId == nil and escaped and mode == "fire" and model.Parent and prompt.Parent then
fireMisses = fireMisses + 1 -- target still there but no Tool: count it against the instant trigger
if fireMisses >= 2 then
fireUnreliable = true
end
end
break
end
end
for _, c in ipairs(conns) do
c:Disconnect()
end
if newId ~= nil then
fireMisses = 0
return true, { id = newId }
end
return false
end
local Remotes = ReplicatedStorage:FindFirstChild("Remotes")
local Placer = {}
-- Runs Shop:InvokeServer(action, id) and reports whether the inventory really changed.
local function inventoryCall(action, id)
local shop = Remotes and Remotes:FindFirstChild("Shop")
if not (shop and shop:IsA("RemoteFunction")) then
return false, "Remotes.Shop missing"
end
local hadTool = toolIds()[id] == true
local invChanged = false
local conn = lp:GetAttributeChangedSignal("Inv"):Connect(function()
invChanged = true
end)
-- InvokeServer yields until the server answers; run it on its own thread so a silent server cannot freeze the loop
local done, ok, r1, r2 = false, nil, nil, nil
task.spawn(function()
ok, r1, r2 = pcall(function()
return shop:InvokeServer(action, id)
end)
done = true
end)
local invokeDeadline = os.clock() + CFG.invokeTimeout
while not done and os.clock() < invokeDeadline and State.alive do
task.wait()
end
local function confirmed()
return invChanged or (hadTool and toolIds()[id] ~= true)
end
local deadline = os.clock() + CFG.confirmTimeout
while not confirmed() and os.clock() < deadline and State.alive do
task.wait()
end
conn:Disconnect()
local changed = confirmed()
if changed and action == "placeInv" then
timeline.tPlaced = os.clock()
end
return changed, string.format("%s done=%s ok=%s r1=%s r2=%s", action, tostring(done), tostring(ok), tostring(r1), tostring(r2))
end
function Placer.place(id)
return inventoryCall("placeInv", id)
end
-- permanent: only reached when the user chose "Sell" for a full pen
function Placer.sell(id)
return inventoryCall("sellInv", id)
end
function Placer.holds(id)
return toolIds()[id] == true
end
local ctl = Core.newController({
source = Source,
mover = Mover,
actor = Actor,
placer = Placer,
control = { running = running },
clock = { now = os.clock },
}, CFG.core)
-- ===================================================================================================
-- Part 3: Kryven UI toolkit
-- ===================================================================================================
-- Enum.KeyCode[name] throws on an unknown name; this returns nil instead
local function keyCode(name)
local ok, value = pcall(function()
return Enum.KeyCode[name]
end)
return ok and value or nil
end
local T = {
bg = Color3.fromRGB(13, 15, 15),
panel = Color3.fromRGB(16, 19, 19),
card = Color3.fromRGB(19, 23, 22),
hover = Color3.fromRGB(27, 33, 31),
line = Color3.fromRGB(33, 39, 38),
text = Color3.fromRGB(236, 240, 238),
dim = Color3.fromRGB(121, 133, 129),
accent = Color3.fromRGB(34, 197, 94),
accentDim = Color3.fromRGB(18, 44, 32),
accentText = Color3.fromRGB(10, 22, 14),
off = Color3.fromRGB(46, 52, 51),
offKnob = Color3.fromRGB(112, 122, 119),
danger = Color3.fromRGB(239, 68, 68),
}
local W, H = 780, 500
local TOP, STATUS, SIDE = 52, 30, 176
local function new(class, props, parent)
local inst = Instance.new(class)
for k, v in pairs(props or {}) do
inst[k] = v
end
if parent then
inst.Parent = parent
end
return inst
end
local function corner(inst, radius)
return new("UICorner", { CornerRadius = UDim.new(0, radius) }, inst)
end
local function stroke(inst, color, thickness)
return new("UIStroke", {
Color = color, Thickness = thickness or 1, ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
}, inst)
end
local function listLayout(inst, direction, padding)
return new("UIListLayout", {
FillDirection = direction or Enum.FillDirection.Vertical,
Padding = UDim.new(0, padding or 0),
SortOrder = Enum.SortOrder.LayoutOrder,
}, inst)
end
local function makeText(props, parent)
local p = {
BackgroundTransparency = 1,
Font = Enum.Font.Gotham,
TextColor3 = T.text,
TextSize = 13,
TextXAlignment = Enum.TextXAlignment.Left,
TextTruncate = Enum.TextTruncate.AtEnd,
}
for k, v in pairs(props) do
p[k] = v
end
return new("TextLabel", p, parent)
end
local function tween(inst, seconds, props, style)
local t = TweenService:Create(inst, TweenInfo.new(seconds, style or Enum.EasingStyle.Quad, Enum.EasingDirection.Out), props)
t:Play()
return t
end
local gui = new("ScreenGui", {
Name = HUB_NAME,
ResetOnSpawn = false,
DisplayOrder = 999,
IgnoreGuiInset = true,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
})
local function mountGui()
local candidates = {}
if type(gethui) == "function" then
local ok, h = pcall(gethui)
if ok and h then
candidates[#candidates + 1] = h
end
end
local okCore, core = pcall(game.GetService, game, "CoreGui")
if okCore then
candidates[#candidates + 1] = core
end
candidates[#candidates + 1] = lp:WaitForChild("PlayerGui")
for _, parent in ipairs(candidates) do
for _, name in ipairs({ HUB_NAME, "AutoStealV3HUD", "AutoStealV2HUD" }) do
local old = parent:FindFirstChild(name)
if old then
old:Destroy()
end
end
if pcall(function() gui.Parent = parent end) and gui.Parent == parent then
return true
end
end
return false
end
-- Drag `target` by `handle`; a press that barely moves counts as a click.
local function draggable(handle, target, onClick)
local dragging, moved, startMouse, startPos = false, false, nil, nil
bind(handle.InputBegan, function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging, moved = true, false
startMouse, startPos = input.Position, target.Position
end
end)
bind(UserInputService.InputChanged, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
local delta = input.Position - startMouse
if delta.Magnitude > 4 then
moved = true
end
if moved then
target.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.X,
startPos.Y.Scale, startPos.Y.Offset + delta.Y)
end
end
end)
bind(UserInputService.InputEnded, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then
dragging = false
if not moved and onClick then
safe(onClick)
end
end
end)
end
local function makeIcon(kind, parent)
local box = new("Frame", {
Size = UDim2.fromOffset(14, 14), Position = UDim2.new(0, 14, 0.5, -7), BackgroundTransparency = 1,
}, parent)
local fills, strokes = {}, {}
if kind == "diamond" then
fills[1] = new("Frame", {
Size = UDim2.fromOffset(8, 8), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
Rotation = 45, BorderSizePixel = 0, BackgroundColor3 = T.dim,
}, box)
elseif kind == "dot" then
fills[1] = new("Frame", {
Size = UDim2.fromOffset(8, 8), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
BorderSizePixel = 0, BackgroundColor3 = T.dim,
}, box)
corner(fills[1], 4)
elseif kind == "ring" then
local ring = new("Frame", {
Size = UDim2.fromOffset(10, 10), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
BorderSizePixel = 0, BackgroundTransparency = 1,
}, box)
corner(ring, 5)
strokes[1] = stroke(ring, T.dim, 2)
else
for i = 0, 2 do
fills[#fills + 1] = new("Frame", {
Size = UDim2.fromOffset(10, 2), Position = UDim2.fromOffset(2, 2 + i * 4),
BorderSizePixel = 0, BackgroundColor3 = T.dim,
}, box)
end
end
return {
setColor = function(c)
for _, f in ipairs(fills) do
f.BackgroundColor3 = c
end
for _, s in ipairs(strokes) do
s.Color = c
end
end,
}
end
local Hub = {
tabs = {}, current = nil, flags = {}, controls = {},
ready = false, hidden = false, minimized = false,
notifications = true, blurWanted = false, toggleKey = Enum.KeyCode.RightShift, listening = false,
}
local Tab, Section = {}, {}
Tab.__index = Tab
Section.__index = Section
-- toasts ----------------------------------------------------------------------------------------------
local toastOrder = 0
function Hub:Notify(title, message, force)
if not (self.toastHolder and (self.notifications or force)) then
return
end
toastOrder = toastOrder + 1
local toast = new("Frame", {
Size = UDim2.fromOffset(250, 0), BackgroundColor3 = T.card, BorderSizePixel = 0,
ClipsDescendants = true, LayoutOrder = toastOrder,
}, self.toastHolder)
corner(toast, 8)
stroke(toast, T.line, 1)
new("Frame", { Size = UDim2.new(0, 3, 1, 0), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, toast)
makeText({ Position = UDim2.fromOffset(14, 7), Size = UDim2.new(1, -24, 0, 16), Text = title,
Font = Enum.Font.GothamBold, TextSize = 12 }, toast)
makeText({ Position = UDim2.fromOffset(14, 25), Size = UDim2.new(1, -24, 0, 16), Text = message,
TextSize = 11, TextColor3 = T.dim }, toast)
tween(toast, 0.2, { Size = UDim2.fromOffset(250, 50) })
task.delay(2.6, function()
if toast.Parent then
tween(toast, 0.2, { Size = UDim2.fromOffset(250, 0) })
task.wait(0.25)
toast:Destroy()
end
end)
end
-- dropdown popup --------------------------------------------------------------------------------------
function Hub:_closePopup()
if self.popup then
self.popup.overlay:Destroy()
self.popup.frame:Destroy()
self.popup = nil
end
end
function Hub:_openPopup(anchor, options, current, onPick)
self:_closePopup()
local s = self.window.AbsoluteSize.X / W
local rel = (anchor.AbsolutePosition - self.window.AbsolutePosition) / s
local width, anchorH = anchor.AbsoluteSize.X / s, anchor.AbsoluteSize.Y / s
local height = #options * 28 + 8
local y = rel.Y + anchorH + 4
if y + height > H - STATUS then
y = rel.Y - height - 4
end
local overlay = new("TextButton", {
Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Text = "", AutoButtonColor = false, ZIndex = 40,
}, self.window)
local frame = new("Frame", {
Size = UDim2.fromOffset(width, height), Position = UDim2.fromOffset(rel.X, y),
BackgroundColor3 = T.card, BorderSizePixel = 0, ZIndex = 41,
}, self.window)
corner(frame, 8)
stroke(frame, T.line, 1)
self.popup = { overlay = overlay, frame = frame }
overlay.MouseButton1Click:Connect(function() self:_closePopup() end)
for i, option in ipairs(options) do
local selected = option == current
local b = new("TextButton", {
Size = UDim2.new(1, -8, 0, 26), Position = UDim2.fromOffset(4, 4 + (i - 1) * 28),
BackgroundColor3 = T.accentDim, BackgroundTransparency = selected and 0 or 1, BorderSizePixel = 0,
Text = option, TextColor3 = selected and T.accent or T.text, Font = Enum.Font.Gotham, TextSize = 12,
AutoButtonColor = false, ZIndex = 42,
}, frame)
corner(b, 6)
b.MouseEnter:Connect(function()
if not selected then
tween(b, 0.1, { BackgroundTransparency = 0, BackgroundColor3 = T.hover })
end
end)
b.MouseLeave:Connect(function()
if not selected then
tween(b, 0.1, { BackgroundTransparency = 1 })
end
end)
b.MouseButton1Click:Connect(function()
self:_closePopup()
onPick(option)
end)
end
end
-- sections and controls -------------------------------------------------------------------------------
function Section:_refresh()
local total, on = 0, 0
for _, c in ipairs(self.toggles) do
total = total + 1
if c.value then
on = on + 1
end
end
self.countLabel.Text = total > 0 and (on .. "/" .. total .. " on") or ""
Hub:_updateHeader()
end
function Section:_row(name, desc, height)
local row = new("Frame", {
Size = UDim2.new(1, 0, 0, height), BackgroundTransparency = 1, LayoutOrder = #self.rows + 1,
}, self.body)
new("Frame", { Size = UDim2.new(1, 0, 0, 1), BackgroundColor3 = T.line, BorderSizePixel = 0 }, row)
if name then
if desc then
makeText({ Position = UDim2.fromOffset(16, 8), Size = UDim2.new(1, -150, 0, 18), Text = name,
Font = Enum.Font.GothamMedium, TextSize = 13 }, row)
makeText({ Position = UDim2.fromOffset(16, 27), Size = UDim2.new(1, -150, 0, 14), Text = desc,
TextSize = 11, TextColor3 = T.dim }, row)
else
makeText({ Position = UDim2.fromOffset(16, 0), Size = UDim2.new(1, -150, 1, 0), Text = name,
Font = Enum.Font.GothamMedium, TextSize = 13 }, row)
end
end
self.rows[#self.rows + 1] = { frame = row, key = ((name or "") .. " " .. (desc or "")):lower() }
return row
end
function Section:_register(ctrl, o, isFeature)
ctrl.default = ctrl.value
ctrl.noReset = o.noReset == true
self.tab.controls[#self.tab.controls + 1] = ctrl
if isFeature then
self.tab.featureCount = self.tab.featureCount + 1
self.tab.countLabel.Text = tostring(self.tab.featureCount)
end
if o.flag then
Hub.controls[o.flag] = ctrl
if not o.noSave then
Hub.flags[o.flag] = ctrl.value
end
end
end
local function emit(o, ctrl, flagSaved)
if o.flag and flagSaved then
Hub.flags[o.flag] = ctrl.value
end
if o.callback then
safe(o.callback, ctrl.value)
end
end
function Section:Toggle(o)
local row = self:_row(o.name, o.desc, o.desc and 52 or 44)
local track = new("TextButton", {
Size = UDim2.fromOffset(40, 22), AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -16, 0.5, 0),
BackgroundColor3 = T.off, BorderSizePixel = 0, Text = "", AutoButtonColor = false,
}, row)
corner(track, 11)
local knob = new("Frame", {
Size = UDim2.fromOffset(16, 16), Position = UDim2.fromOffset(3, 3), BackgroundColor3 = T.offKnob, BorderSizePixel = 0,
}, track)
corner(knob, 8)
local ctrl = { kind = "toggle", value = false }
local section = self
local function visual(v)
tween(track, 0.15, { BackgroundColor3 = v and T.accent or T.off })
tween(knob, 0.15, {
Position = v and UDim2.fromOffset(21, 3) or UDim2.fromOffset(3, 3),
BackgroundColor3 = v and T.accentText or T.offKnob,
})
end
function ctrl.Set(v, silent)
v = v == true
if v == ctrl.value and not silent then
return
end
ctrl.value = v
visual(v)
section:_refresh()
if not silent then
emit(o, ctrl, not o.noSave)
if Hub.ready and o.notify ~= false then
Hub:Notify(o.name, v and "Enabled" or "Disabled")
end
end
end
track.MouseButton1Click:Connect(function()
ctrl.Set(not ctrl.value)
end)
ctrl.value = o.default == true
visual(ctrl.value)
self.toggles[#self.toggles + 1] = ctrl
self:_register(ctrl, o, true)
self:_refresh()
if o.callback then
safe(o.callback, ctrl.value)
end
return ctrl
end
function Section:Slider(o)
local row = self:_row(o.name, nil, 58)
local step = o.step or 1
local fmt = o.format or function(v) return tostring(v) end
local valueLabel = makeText({
AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -16, 0, 8), Size = UDim2.fromOffset(130, 18),
TextXAlignment = Enum.TextXAlignment.Right, RichText = true, Font = Enum.Font.RobotoMono, TextSize = 12,
}, row)
local track = new("Frame", {
Position = UDim2.fromOffset(16, 40), Size = UDim2.new(1, -32, 0, 4), BackgroundColor3 = T.off, BorderSizePixel = 0,
}, row)
corner(track, 2)
local fill = new("Frame", { Size = UDim2.fromScale(0, 1), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, track)
corner(fill, 2)
local knob = new("Frame", {
Size = UDim2.fromOffset(14, 14), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0, 0.5),
BackgroundColor3 = T.text, BorderSizePixel = 0, ZIndex = 2,
}, track)
corner(knob, 7)
local hit = new("TextButton", {
Position = UDim2.fromOffset(16, 30), Size = UDim2.new(1, -32, 0, 24), BackgroundTransparency = 1, Text = "",
AutoButtonColor = false,
}, row)
local ctrl = { kind = "slider", value = o.min }
local function render(v)
local frac = (v - o.min) / math.max(o.max - o.min, 1e-9)
fill.Size = UDim2.fromScale(frac, 1)
knob.Position = UDim2.fromScale(frac, 0.5)
valueLabel.Text = string.format('<b>%s</b> <font color="rgb(121,133,129)">/ %s</font>', fmt(v), fmt(o.max))
end
function ctrl.Set(v, silent)
v = o.min + math.floor((v - o.min) / step + 0.5) * step
v = math.clamp(v, o.min, o.max)
if v == ctrl.value and not silent then
return
end
ctrl.value = v
render(v)
if not silent then
emit(o, ctrl, not o.noSave)
end
end
local dragging = false
local function fromInput(input)
local rel = math.clamp((input.Position.X - track.AbsolutePosition.X) / math.max(track.AbsoluteSize.X, 1), 0, 1)
ctrl.Set(o.min + rel * (o.max - o.min))
end
hit.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
fromInput(input)
end
end)
bind(UserInputService.InputChanged, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
fromInput(input)
end
end)
bind(UserInputService.InputEnded, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then
dragging = false
end
end)
ctrl.value = math.clamp(o.default or o.min, o.min, o.max)
render(ctrl.value)
self:_register(ctrl, o, true)
if o.callback then
safe(o.callback, ctrl.value)
end
return ctrl
end
function Section:Dropdown(o)
local row = self:_row(o.name, o.desc, o.desc and 52 or 44)
local btn = new("TextButton", {
Size = UDim2.fromOffset(136, 28), AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -16, 0.5, 0),
BackgroundColor3 = T.panel, BorderSizePixel = 0, Text = "", AutoButtonColor = false,
}, row)
corner(btn, 6)
stroke(btn, T.line, 1)
local txt = makeText({ Position = UDim2.fromOffset(10, 0), Size = UDim2.new(1, -28, 1, 0), TextSize = 12 }, btn)
makeText({ Position = UDim2.new(1, -20, 0, 0), Size = UDim2.fromOffset(16, 28), Text = "v", Font = Enum.Font.RobotoMono,
TextSize = 11, TextColor3 = T.dim, TextXAlignment = Enum.TextXAlignment.Center }, btn)
local ctrl = { kind = "dropdown", value = o.options[1] }
function ctrl.Set(v, silent)
local known = false
for _, option in ipairs(o.options) do
if option == v then
known = true
end
end
if not known or (v == ctrl.value and not silent) then
return
end
ctrl.value = v
txt.Text = v
if not silent then
emit(o, ctrl, not o.noSave)
end
end
btn.MouseButton1Click:Connect(function()
Hub:_openPopup(btn, o.options, ctrl.value, function(option)
ctrl.Set(option)
end)
end)
btn.MouseEnter:Connect(function() tween(btn, 0.1, { BackgroundColor3 = T.hover }) end)
btn.MouseLeave:Connect(function() tween(btn, 0.1, { BackgroundColor3 = T.panel }) end)
ctrl.value = o.default or o.options[1]
txt.Text = ctrl.value
self:_register(ctrl, o, true)
if o.callback then
safe(o.callback, ctrl.value)
end
return ctrl
end
function Section:Keybind(o)
local row = self:_row(o.name, o.desc, o.desc and 52 or 44)
local btn = new("TextButton", {
Size = UDim2.fromOffset(110, 28), AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -16, 0.5, 0),
BackgroundColor3 = T.panel, BorderSizePixel = 0, AutoButtonColor = false, Text = "",
Font = Enum.Font.RobotoMono, TextSize = 12, TextColor3 = T.text,
}, row)
corner(btn, 6)
stroke(btn, T.line, 1)
local ctrl = { kind = "keybind", value = o.default or "RightShift" }
function ctrl.Set(v, silent)
if keyCode(v) == nil then
return
end
ctrl.value = v
btn.Text = v
if not silent then
emit(o, ctrl, not o.noSave)
end
end
btn.MouseButton1Click:Connect(function()
Hub.listening = true
btn.Text = "press a key..."
local conn
conn = UserInputService.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.Keyboard then
conn:Disconnect()
Hub.listening = false
if input.KeyCode ~= Enum.KeyCode.Escape then
ctrl.Set(input.KeyCode.Name)
else
btn.Text = ctrl.value
end
end
end)
end)
btn.Text = ctrl.value
self:_register(ctrl, o, true)
if o.callback then
safe(o.callback, ctrl.value)
end
return ctrl
end
-- o.buttons = { { text = "...", callback = fn, accent = bool }, ... }
function Section:Buttons(o)
local row = self:_row(o.name, o.desc, o.desc and 52 or 44)
local x = 16
for i = #o.buttons, 1, -1 do
local spec = o.buttons[i]
local width = spec.width or 84
local b = new("TextButton", {
Size = UDim2.fromOffset(width, 28), AnchorPoint = Vector2.new(1, 0.5), Position = UDim2.new(1, -x, 0.5, 0),
BackgroundColor3 = spec.accent and T.accentDim or T.panel, BorderSizePixel = 0, AutoButtonColor = false,
Text = spec.text, TextColor3 = spec.accent and T.accent or (spec.danger and T.danger or T.text),
Font = Enum.Font.GothamMedium, TextSize = 12,
}, row)
corner(b, 6)
stroke(b, spec.accent and T.accent or T.line, 1)
local base = spec.accent and T.accentDim or T.panel
b.MouseEnter:Connect(function() tween(b, 0.1, { BackgroundColor3 = T.hover }) end)
b.MouseLeave:Connect(function() tween(b, 0.1, { BackgroundColor3 = base }) end)
b.MouseButton1Click:Connect(function()
if spec.callback then
safe(spec.callback)
end
end)
x = x + width + 8
end
return row
end
function Section:Info(o)
local row = self:_row(o.name, nil, 38)
local value = makeText({
AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -16, 0, 0), Size = UDim2.new(0.55, -16, 1, 0),
Text = o.value or "", TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.RobotoMono, TextSize = 11,
TextColor3 = T.dim,
}, row)
return { Set = function(text) value.Text = text end }
end
-- an empty, auto-sized frame for dynamic content (area lists)
function Section:Container(name)
local row = self:_row(nil, nil, 0)
row.Size = UDim2.new(1, 0, 0, 0)
row.AutomaticSize = Enum.AutomaticSize.Y
self.rows[#self.rows].key = (name or ""):lower()
local box = new("Frame", {
Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1,
}, row)
new("UIPadding", { PaddingTop = UDim.new(0, 6), PaddingBottom = UDim.new(0, 8), PaddingLeft = UDim.new(0, 8),
PaddingRight = UDim.new(0, 8) }, box)
listLayout(box, Enum.FillDirection.Vertical, 4)
return box
end
function Tab:Section(title, col)
col = col or ((#self.sections % 2) + 1)
local card = new("Frame", {
Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundColor3 = T.card, BorderSizePixel = 0,
LayoutOrder = #self.sections + 1,
}, self.colFrames[col])
corner(card, 10)
stroke(card, T.line, 1)
listLayout(card, Enum.FillDirection.Vertical, 0)
local header = new("Frame", { Size = UDim2.new(1, 0, 0, 36), BackgroundTransparency = 1, LayoutOrder = 0 }, card)
makeText({ Position = UDim2.fromOffset(16, 0), Size = UDim2.new(0.6, 0, 1, 0), Text = title,
Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim }, header)
local countLabel = makeText({
AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -16, 0, 0), Size = UDim2.new(0.4, 0, 1, 0), Text = "",
TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim,
}, header)
local body = new("Frame", {
Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1, LayoutOrder = 1,
}, card)
listLayout(body, Enum.FillDirection.Vertical, 0)
local sec = setmetatable({
tab = self, card = card, body = body, countLabel = countLabel, rows = {}, toggles = {}, titleKey = title:lower(),
}, Section)
self.sections[#self.sections + 1] = sec
return sec
end
function Tab:ActiveCount()
local n = 0
for _, sec in ipairs(self.sections) do
for _, c in ipairs(sec.toggles) do
if c.value then
n = n + 1
end
end
end
return n
end
function Hub:_updateHeader()
local tab = self.current
if not (tab and self.activeText) then
return
end
local n = tab:ActiveCount()
self.activeText.Text = n .. " active"
self.activeDot.BackgroundColor3 = n > 0 and T.accent or T.dim
end
function Hub:AddTab(name, iconKind, subtitle)
local index = #self.tabs + 1
local tab = setmetatable({
name = name, subtitle = subtitle or "", sections = {}, controls = {}, featureCount = 0,
}, Tab)
local btn = new("TextButton", {
Size = UDim2.new(1, -16, 0, 36), BackgroundColor3 = T.accentDim, BackgroundTransparency = 1, BorderSizePixel = 0,
Text = "", AutoButtonColor = false, LayoutOrder = index,
}, self.sideList)
corner(btn, 8)
tab.bar = new("Frame", {
Size = UDim2.fromOffset(3, 18), Position = UDim2.new(0, 0, 0.5, -9), BackgroundColor3 = T.accent,
BorderSizePixel = 0, Visible = false,
}, btn)
corner(tab.bar, 2)
tab.icon = makeIcon(iconKind, btn)
tab.label = makeText({
Position = UDim2.fromOffset(38, 0), Size = UDim2.new(1, -70, 1, 0), Text = name, Font = Enum.Font.GothamMedium,
TextSize = 14, TextColor3 = T.dim,
}, btn)
tab.countLabel = makeText({
AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -12, 0, 0), Size = UDim2.fromOffset(24, 36), Text = "0",
TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim,
}, btn)
tab.btn = btn
btn.MouseEnter:Connect(function()
if self.current ~= tab then
tween(btn, 0.1, { BackgroundColor3 = T.card, BackgroundTransparency = 0 })
end
end)
btn.MouseLeave:Connect(function()
if self.current ~= tab then
tween(btn, 0.1, { BackgroundTransparency = 1 })
end
end)
btn.MouseButton1Click:Connect(function()
self:Select(tab)
end)
tab.page = new("ScrollingFrame", {
Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, BorderSizePixel = 0, ScrollBarThickness = 3,
ScrollBarImageColor3 = T.line, CanvasSize = UDim2.new(), AutomaticCanvasSize = Enum.AutomaticSize.Y,
Visible = false,
}, self.pages)
new("UIPadding", { PaddingLeft = UDim.new(0, 24), PaddingRight = UDim.new(0, 24), PaddingTop = UDim.new(0, 4),
PaddingBottom = UDim.new(0, 16) }, tab.page)
listLayout(tab.page, Enum.FillDirection.Vertical, 0)
local cols = new("Frame", { Size = UDim2.new(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1 }, tab.page)
listLayout(cols, Enum.FillDirection.Horizontal, 12)
tab.colFrames = {}
for i = 1, 2 do
local col = new("Frame", {
Size = UDim2.new(0.5, -6, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1, LayoutOrder = i,
}, cols)
listLayout(col, Enum.FillDirection.Vertical, 12)
tab.colFrames[i] = col
end
self.tabs[index] = tab
return tab
end
function Hub:Select(tab)
if self.current == tab then
return
end
self:_closePopup()
self.current = tab
for _, t in ipairs(self.tabs) do
local on = t == tab
t.page.Visible = on
t.bar.Visible = on
t.btn.BackgroundColor3 = on and T.accentDim or T.card
t.btn.BackgroundTransparency = on and 0 or 1
tween(t.label, 0.15, { TextColor3 = on and T.text or T.dim })
t.icon.setColor(on and T.accent or T.dim)
t.countLabel.TextColor3 = on and T.accent or T.dim
end
self.title.Text = tab.name
self.subtitle.Text = tab.subtitle
self.cover.BackgroundTransparency = 0
tween(self.cover, 0.22, { BackgroundTransparency = 1 })
self:_updateHeader()
self:Filter(self.searchBox.Text)
end
function Hub:Filter(text)
text = (text or ""):lower()
local tab = self.current
if not tab then
return
end
for _, sec in ipairs(tab.sections) do
local any = false
for _, r in ipairs(sec.rows) do
local show = text == "" or r.key:find(text, 1, true) ~= nil
r.frame.Visible = show
any = any or show
end
sec.card.Visible = text == "" or any or sec.titleKey:find(text, 1, true) ~= nil
end
end
function Hub:ResetTab()
local tab = self.current
if not tab then
return
end
for _, c in ipairs(tab.controls) do
if not c.noReset then
c.Set(c.default)
end
end
self:Notify(tab.name, "Tab reset")
end
-- window state ----------------------------------------------------------------------------------------
function Hub:_applyBlur()
if self.blurWanted and not self.blur then
self.blur = new("BlurEffect", { Size = 12, Enabled = false }, Lighting)
end
if self.blur then
self.blur.Enabled = self.blurWanted and not self.hidden and not self.minimized
end
end
function Hub:Open()
self.holder.Visible = true
self.window.GroupTransparency = 1
self.openScale.Scale = 0.94
tween(self.window, 0.3, { GroupTransparency = 0 })
tween(self.openScale, 0.3, { Scale = 1 }, Enum.EasingStyle.Back)
self:_applyBlur()
end
function Hub:Minimize()
if self.minimized then
return
end
self.minimized = true
self:_closePopup()
tween(self.window, 0.18, { GroupTransparency = 1 })
tween(self.openScale, 0.18, { Scale = 0.9 })
task.delay(0.2, function()
if self.minimized then
self.holder.Visible = false
end
end)
self.icon.Visible = not self.hidden
self.iconScale.Scale = 0.5
tween(self.iconScale, 0.25, { Scale = 1 }, Enum.EasingStyle.Back)
self:_applyBlur()
end
function Hub:Restore()
self.minimized = false
self.icon.Visible = false
self:Open()
end
function Hub:SetHidden(hide)
if hide == self.hidden then
return
end
self.hidden = hide
self:_closePopup()
if hide then
self.holder.Visible = false
self.icon.Visible = false
elseif self.minimized then
self.icon.Visible = true
else
self:Open()
end
self:_applyBlur()
end
-- config ----------------------------------------------------------------------------------------------
local CONFIG_DIR = "kryven_hub"
local function configPath()
return CONFIG_DIR .. "/" .. tostring(game.PlaceId) .. ".json"
end
function Hub:SaveConfig()
if type(writefile) ~= "function" then
self:Notify("Config", "writefile is not available", true)
return false
end
if type(isfolder) == "function" and type(makefolder) == "function" and not isfolder(CONFIG_DIR) then
pcall(makefolder, CONFIG_DIR)
end
local okEncode, json = pcall(HttpService.JSONEncode, HttpService, self.flags)
if not okEncode then
self:Notify("Config", "Could not encode settings", true)
return false
end
local okWrite = pcall(writefile, configPath(), json)
self:Notify("Config", okWrite and "Saved" or "Save failed", true)
return okWrite
end
function Hub:LoadConfig(silent)
if type(isfile) ~= "function" or type(readfile) ~= "function" or not isfile(configPath()) then
if not silent then
self:Notify("Config", "No saved config for this place", true)
end
return false
end
local okRead, raw = pcall(readfile, configPath())
local okDecode, data = false, nil
if okRead then
okDecode, data = pcall(HttpService.JSONDecode, HttpService, raw)
end
if not (okDecode and type(data) == "table") then
if not silent then
self:Notify("Config", "Config file is unreadable", true)
end
return false
end
if silent and data.autoload == false then
return false
end
for flag, value in pairs(data) do
local c = self.controls[flag]
if c then
safe(c.Set, value)
end
end
if not silent then
self:Notify("Config", "Loaded", true)
end
return true
end
-- window construction ---------------------------------------------------------------------------------
function Hub:Build(gameName)
local holder = new("Frame", {
Name = "Holder", Size = UDim2.fromOffset(W, H), AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5), BackgroundTransparency = 1, Visible = false,
}, gui)
self.holder = holder
self.userScale = new("UIScale", { Scale = 1 }, holder)
local window = new("CanvasGroup", {
Size = UDim2.fromScale(1, 1), BackgroundColor3 = T.bg, BorderSizePixel = 0, GroupTransparency = 1,
}, holder)
corner(window, 12)
stroke(window, T.line, 1)
self.window = window
self.openScale = new("UIScale", { Scale = 0.94 }, window)
-- top bar
local top = new("Frame", { Size = UDim2.new(1, 0, 0, TOP), BackgroundTransparency = 1, Active = true }, window)
new("Frame", { Size = UDim2.new(1, 0, 0, 1), Position = UDim2.new(0, 0, 1, -1), BackgroundColor3 = T.line, BorderSizePixel = 0 }, top)
local logo = new("Frame", { Size = UDim2.fromOffset(28, 28), Position = UDim2.fromOffset(16, 12), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, top)
corner(logo, 7)
makeText({ Size = UDim2.fromScale(1, 1), Text = "K", Font = Enum.Font.GothamBold, TextSize = 16,
TextColor3 = T.accentText, TextXAlignment = Enum.TextXAlignment.Center }, logo)
makeText({ Position = UDim2.fromOffset(54, 0), Size = UDim2.fromOffset(70, TOP), Text = "Kryven",
Font = Enum.Font.GothamBold, TextSize = 17 }, top)
local badge = new("Frame", { Position = UDim2.fromOffset(124, 16), Size = UDim2.fromOffset(66, 20), BackgroundColor3 = T.accentDim, BorderSizePixel = 0 }, top)
corner(badge, 5)
stroke(badge, T.accent, 1)
makeText({ Size = UDim2.fromScale(1, 1), Text = "PREMIUM", Font = Enum.Font.RobotoMono, TextSize = 10,
TextColor3 = T.accent, TextXAlignment = Enum.TextXAlignment.Center }, badge)
makeText({ Position = UDim2.fromOffset(200, 0), Size = UDim2.fromOffset(12, TOP), Text = "/", TextColor3 = T.dim }, top)
makeText({ Position = UDim2.fromOffset(216, 0), Size = UDim2.fromOffset(190, TOP), Text = gameName,
TextColor3 = T.text, TextSize = 14 }, top)
local searchFrame = new("Frame", {
Position = UDim2.fromOffset(W - 16 - 28 - 8 - 28 - 8 - 180, 11), Size = UDim2.fromOffset(180, 30),
BackgroundColor3 = T.panel, BorderSizePixel = 0,
}, top)
corner(searchFrame, 8)
stroke(searchFrame, T.line, 1)
local lens = new("Frame", { Position = UDim2.fromOffset(10, 9), Size = UDim2.fromOffset(11, 11), BackgroundTransparency = 1 }, searchFrame)
corner(lens, 6)
stroke(lens, T.dim, 1.5)
self.searchBox = new("TextBox", {
Position = UDim2.fromOffset(30, 0), Size = UDim2.new(1, -38, 1, 0), BackgroundTransparency = 1, Text = "",
PlaceholderText = "Search features", PlaceholderColor3 = T.dim, TextColor3 = T.text, Font = Enum.Font.Gotham,
TextSize = 12, TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = false,
}, searchFrame)
bind(self.searchBox:GetPropertyChangedSignal("Text"), function()
self:Filter(self.searchBox.Text)
end)
local function topButton(text, x, onClick)
local b = new("TextButton", {
Position = UDim2.fromOffset(x, 12), Size = UDim2.fromOffset(28, 28), BackgroundColor3 = T.panel, BorderSizePixel = 0,
Text = text, TextColor3 = T.dim, Font = Enum.Font.GothamBold, TextSize = 14, AutoButtonColor = false,
}, top)
corner(b, 8)
b.MouseEnter:Connect(function() tween(b, 0.1, { BackgroundColor3 = T.hover, TextColor3 = T.text }) end)
b.MouseLeave:Connect(function() tween(b, 0.1, { BackgroundColor3 = T.panel, TextColor3 = T.dim }) end)
b.MouseButton1Click:Connect(function() safe(onClick) end)
end
topButton("-", W - 16 - 28 - 8 - 28, function() self:Minimize() end)
topButton("x", W - 16 - 28, function()
self:SetHidden(true)
self:Notify("Kryven", "Press " .. self.toggleKey.Name .. " to show the hub", true)
end)
draggable(top, holder)
-- sidebar
local side = new("Frame", { Position = UDim2.fromOffset(0, TOP), Size = UDim2.fromOffset(SIDE, H - TOP - STATUS), BackgroundTransparency = 1 }, window)
new("Frame", { Size = UDim2.new(0, 1, 1, 0), Position = UDim2.new(1, -1, 0, 0), BackgroundColor3 = T.line, BorderSizePixel = 0 }, side)
makeText({ Position = UDim2.fromOffset(18, 14), Size = UDim2.fromOffset(120, 16), Text = "FEATURES",
Font = Enum.Font.RobotoMono, TextSize = 10, TextColor3 = T.dim }, side)
self.sideList = new("Frame", { Position = UDim2.fromOffset(8, 38), Size = UDim2.new(1, -17, 1, -110), BackgroundTransparency = 1 }, side)
listLayout(self.sideList, Enum.FillDirection.Vertical, 4)
local userCard = new("Frame", {
Position = UDim2.new(0, 8, 1, -60), Size = UDim2.new(1, -17, 0, 52), BackgroundColor3 = T.card, BorderSizePixel = 0,
}, side)
corner(userCard, 10)
stroke(userCard, T.line, 1)
local avatar = new("ImageLabel", {
Position = UDim2.fromOffset(10, 10), Size = UDim2.fromOffset(32, 32), BackgroundColor3 = T.panel, BorderSizePixel = 0, Image = "",
}, userCard)
corner(avatar, 16)
task.spawn(function()
local ok, img = pcall(Players.GetUserThumbnailAsync, Players, lp.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size100x100)
if ok and avatar.Parent then
avatar.Image = img
end
end)
makeText({ Position = UDim2.fromOffset(52, 9), Size = UDim2.new(1, -60, 0, 18), Text = lp.DisplayName,
Font = Enum.Font.GothamMedium, TextSize = 12 }, userCard)
makeText({ Position = UDim2.fromOffset(52, 28), Size = UDim2.new(1, -60, 0, 14), Text = "@" .. lp.Name,
Font = Enum.Font.RobotoMono, TextSize = 10, TextColor3 = T.dim }, userCard)
-- content header + pages
local content = new("Frame", {
Position = UDim2.fromOffset(SIDE, TOP), Size = UDim2.fromOffset(W - SIDE, H - TOP - STATUS), BackgroundTransparency = 1,
}, window)
self.title = makeText({ Position = UDim2.fromOffset(24, 12), Size = UDim2.fromOffset(260, 28), Text = "",
Font = Enum.Font.GothamBold, TextSize = 22 }, content)
self.subtitle = makeText({ Position = UDim2.fromOffset(24, 40), Size = UDim2.fromOffset(300, 16), Text = "",
TextSize = 12, TextColor3 = T.dim }, content)
local contentW = W - SIDE
local reset = new("TextButton", {
Position = UDim2.fromOffset(contentW - 24 - 78, 18), Size = UDim2.fromOffset(78, 28), BackgroundColor3 = T.panel,
BorderSizePixel = 0, Text = "Reset tab", TextColor3 = T.text, Font = Enum.Font.GothamMedium, TextSize = 12,
AutoButtonColor = false,
}, content)
corner(reset, 8)
stroke(reset, T.line, 1)
reset.MouseEnter:Connect(function() tween(reset, 0.1, { BackgroundColor3 = T.hover }) end)
reset.MouseLeave:Connect(function() tween(reset, 0.1, { BackgroundColor3 = T.panel }) end)
reset.MouseButton1Click:Connect(function() self:ResetTab() end)
local pill = new("Frame", {
Position = UDim2.fromOffset(contentW - 24 - 78 - 8 - 92, 18), Size = UDim2.fromOffset(92, 28), BackgroundColor3 = T.panel,
BorderSizePixel = 0,
}, content)
corner(pill, 8)
stroke(pill, T.line, 1)
self.activeDot = new("Frame", { Position = UDim2.fromOffset(12, 10), Size = UDim2.fromOffset(8, 8), BackgroundColor3 = T.dim, BorderSizePixel = 0 }, pill)
corner(self.activeDot, 4)
self.activeText = makeText({ Position = UDim2.fromOffset(26, 0), Size = UDim2.new(1, -30, 1, 0), Text = "0 active", TextSize = 12 }, pill)
self.pages = new("Frame", { Position = UDim2.fromOffset(0, 64), Size = UDim2.new(1, 0, 1, -64), BackgroundTransparency = 1 }, content)
self.cover = new("Frame", {
Position = UDim2.fromOffset(0, 64), Size = UDim2.new(1, 0, 1, -64), BackgroundColor3 = T.bg, BorderSizePixel = 0,
BackgroundTransparency = 1, ZIndex = 10,
}, content)
-- status bar
local status = new("Frame", { Position = UDim2.new(0, 0, 1, -STATUS), Size = UDim2.new(1, 0, 0, STATUS), BackgroundTransparency = 1 }, window)
new("Frame", { Size = UDim2.new(1, 0, 0, 1), BackgroundColor3 = T.line, BorderSizePixel = 0 }, status)
local dot = new("Frame", { Position = UDim2.fromOffset(18, 11), Size = UDim2.fromOffset(8, 8), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, status)
corner(dot, 4)
local left = makeText({ Position = UDim2.fromOffset(34, 0), Size = UDim2.new(0.5, 0, 1, 0), Font = Enum.Font.RobotoMono,
TextSize = 11, TextColor3 = T.dim, Text = "" }, status)
local right = makeText({ AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -18, 0, 0), Size = UDim2.new(0.5, 0, 1, 0),
TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim, Text = "" }, status)
local frames, fps, started = 0, 0, os.clock()
bind(RunService.Heartbeat, function()
frames = frames + 1
end)
task.spawn(function()
while State.alive do
fps, frames = frames, 0
local ping = "?"
local ok, value = pcall(function()
return Stats.Network.ServerStatsItem["Data Ping"]:GetValue()
end)
if ok and value then
ping = tostring(math.floor(value + 0.5))
end
local elapsed = math.floor(os.clock() - started)
left.Text = string.format("Connected Place %s Server %d/%d", tostring(game.PlaceId), #Players:GetPlayers(), Players.MaxPlayers)
right.Text = string.format("FPS %d Ping %sms %02d:%02d:%02d %s to hide", fps, ping,
math.floor(elapsed / 3600), math.floor(elapsed / 60) % 60, elapsed % 60, self.toggleKey.Name)
task.wait(1)
end
end)
-- toasts
self.toastHolder = new("Frame", {
Size = UDim2.fromOffset(260, 320), AnchorPoint = Vector2.new(1, 1), Position = UDim2.new(1, -20, 1, -20), BackgroundTransparency = 1,
}, gui)
new("UIListLayout", {
VerticalAlignment = Enum.VerticalAlignment.Bottom, HorizontalAlignment = Enum.HorizontalAlignment.Right,
Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder,
}, self.toastHolder)
-- minimized icon
local icon = new("Frame", {
Name = "Icon", Size = UDim2.fromOffset(52, 52), Position = UDim2.fromOffset(24, 90), BackgroundColor3 = T.panel,
BorderSizePixel = 0, Visible = false, Active = true,
}, gui)
corner(icon, 14)
local iconStroke = stroke(icon, T.line, 1)
self.iconScale = new("UIScale", { Scale = 1 }, icon)
local iconK = new("Frame", { AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5), Size = UDim2.fromOffset(30, 30),
BackgroundColor3 = T.accent, BorderSizePixel = 0 }, icon)
corner(iconK, 8)
makeText({ Size = UDim2.fromScale(1, 1), Text = "K", Font = Enum.Font.GothamBold, TextSize = 18, TextColor3 = T.accentText,
TextXAlignment = Enum.TextXAlignment.Center }, iconK)
icon.MouseEnter:Connect(function() tween(iconStroke, 0.12, { Color = T.accent }) end)
icon.MouseLeave:Connect(function() tween(iconStroke, 0.12, { Color = T.line }) end)
draggable(icon, icon, function() self:Restore() end)
self.icon = icon
-- keyboard toggle
bind(UserInputService.InputBegan, function(input, processed)
if processed or self.listening then
return
end
if input.KeyCode == self.toggleKey then
self:SetHidden(not self.hidden)
end
end)
end
-- splash ----------------------------------------------------------------------------------------------
local function runSplash(gameName, steps)
local card = new("CanvasGroup", {
Size = UDim2.fromOffset(380, 200), AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
BackgroundColor3 = T.panel, BorderSizePixel = 0, GroupTransparency = 1,
}, gui)
corner(card, 14)
stroke(card, T.line, 1)
local cardScale = new("UIScale", { Scale = 0.92 }, card)
local logo = new("Frame", { Size = UDim2.fromOffset(56, 56), Position = UDim2.new(0.5, -28, 0, 26), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, card)
corner(logo, 14)
makeText({ Size = UDim2.fromScale(1, 1), Text = "K", Font = Enum.Font.GothamBold, TextSize = 30, TextColor3 = T.accentText,
TextXAlignment = Enum.TextXAlignment.Center }, logo)
local logoScale = new("UIScale", {}, logo)
local pulse = TweenService:Create(logoScale, TweenInfo.new(0.7, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), { Scale = 1.1 })
pulse:Play()
makeText({ Position = UDim2.fromOffset(0, 92), Size = UDim2.new(1, 0, 0, 28), Text = "Kryven", Font = Enum.Font.GothamBold,
TextSize = 22, TextXAlignment = Enum.TextXAlignment.Center }, card)
makeText({ Position = UDim2.fromOffset(0, 120), Size = UDim2.new(1, 0, 0, 16), Text = gameName, TextSize = 12,
TextColor3 = T.dim, TextXAlignment = Enum.TextXAlignment.Center }, card)
local track = new("Frame", { Position = UDim2.fromOffset(32, 154), Size = UDim2.new(1, -64, 0, 4), BackgroundColor3 = T.off, BorderSizePixel = 0 }, card)
corner(track, 2)
local fill = new("Frame", { Size = UDim2.fromScale(0, 1), BackgroundColor3 = T.accent, BorderSizePixel = 0 }, track)
corner(fill, 2)
local statusLabel = makeText({ Position = UDim2.fromOffset(32, 166), Size = UDim2.new(1, -110, 0, 16), Text = "",
Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim }, card)
local percent = makeText({ AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -32, 0, 166), Size = UDim2.fromOffset(60, 16),
Text = "0%", TextXAlignment = Enum.TextXAlignment.Right, Font = Enum.Font.RobotoMono, TextSize = 11, TextColor3 = T.dim }, card)
tween(card, 0.35, { GroupTransparency = 0 })
tween(cardScale, 0.35, { Scale = 1 }, Enum.EasingStyle.Back)
task.wait(0.35)
for i, step in ipairs(steps) do
statusLabel.Text = step[1] .. "..."
local ok, err = pcall(step[2])
if not ok then
warn("[kryven] step '" .. step[1] .. "' failed: " .. tostring(err))
end
tween(fill, 0.3, { Size = UDim2.fromScale(i / #steps, 1) })
percent.Text = math.floor(i / #steps * 100) .. "%"
task.wait(0.3)
end
statusLabel.Text = "Ready"
task.wait(0.25)
tween(card, 0.3, { GroupTransparency = 1 })
tween(cardScale, 0.3, { Scale = 1.06 })
task.wait(0.32)
pulse:Cancel()
card:Destroy()
end
-- ===================================================================================================
-- Part 4: features wired to the hub
-- ===================================================================================================
local function describe(result, extra)
if result == "no_target" then
return string.format("no target (%d seen, %d filtered, %s)", extra.total, extra.filtered, extra.orderUsed)
elseif result == "source_unavailable" then
return "world not ready: " .. tostring(extra)
elseif result == "paused" then
return string.format("paused %.0fs: %s", extra.remaining, tostring(extra.reason))
elseif result == "deposited" then
local parts = { string.format("placed %d", extra.placed) }
if extra.kept > 0 then
parts[#parts + 1] = string.format("kept %d (pen refused)", extra.kept)
end
if extra.sold > 0 then
parts[#parts + 1] = "sold " .. extra.sold
end
if extra.lost > 0 then
parts[#parts + 1] = "lost " .. extra.lost
end
return table.concat(parts, ", ")
elseif result == "place_failed" then
return "place failed"
elseif type(extra) == "table" and extra.name then
return result .. ": " .. extra.name
end
return result
end
local function entryCount(attrName)
local raw = lp:GetAttribute(attrName)
if type(raw) ~= "string" then
return "?"
end
local ok, decoded = pcall(HttpService.JSONDecode, HttpService, raw)
return (ok and type(decoded) == "table") and #decoded or "?"
end
-- One-line snapshot of what could make a placement fail (pen full, Exu not in hand, ...)
local function placeDiagnostics()
local pen = ownPen()
local exusFolder = pen and pen:FindFirstChild("Exus")
local tools = 0
for _ in pairs(toolIds()) do
tools = tools + 1
end
return string.format("carried=%s | Capacity=%s PenLevel=%s PenEntries=%s PenExusFolder=%s InvEntries=%s Tools=%d",
table.concat(ctl.carried, ","), tostring(lp:GetAttribute("Capacity")), tostring(lp:GetAttribute("PenLevel")),
tostring(entryCount("Pen")), exusFolder and tostring(#exusFolder:GetChildren()) or "?",
tostring(entryCount("Inv")), tools)
end
local function teleportTo(x, y, z)
local _, _, hrp = character()
if not hrp then
Hub:Notify("Teleport", "Character not ready", true)
return
end
hrp.CFrame = CFrame.new(x, y, z)
hrp.AssemblyLinearVelocity = Vector3.zero
end
-- area lists (Farm: pick which areas to steal from; Teleport: jump to an area) ---------------------------
local areaViews = {}
local function areaRows(container, mode)
local lastSignature
local function render()
local areas = ctl.areas
local rows, parts = {}, {}
if areas then
for i, a in ipairs(areas.list) do
local rar = {}
for _, r in ipairs(a.rarities) do
rar[#rar + 1] = r
end
local selected = mode == "select" and Core.isAreaSelected(ctl, i)
rows[#rows + 1] = {
index = i, selected = selected,
title = string.format("Area %d%s", i, i == #areas.list and " (last)" or ""),
detail = string.format("%d alive | %s | @%d,%d", a.alive, #rar > 0 and table.concat(rar, "/") or "-",
math.floor(a.centroid.x + 0.5), math.floor(a.centroid.z + 0.5)),
centroid = a.centroid,
}
parts[#parts + 1] = rows[#rows].title .. rows[#rows].detail .. tostring(selected)
end
end
local signature = table.concat(parts, "\n")
if signature == lastSignature then
return
end
lastSignature = signature
for _, child in ipairs(container:GetChildren()) do
if child:IsA("TextButton") or child:IsA("TextLabel") then
child:Destroy()
end
end
if #rows == 0 then
makeText({ Size = UDim2.new(1, 0, 0, 24), Text = "No Exus seen yet", TextSize = 12, TextColor3 = T.dim }, container)
return
end
for _, r in ipairs(rows) do
local b = new("TextButton", {
Size = UDim2.new(1, 0, 0, 40), BackgroundColor3 = r.selected and T.accentDim or T.panel, BorderSizePixel = 0,
Text = "", AutoButtonColor = false, LayoutOrder = r.index,
}, container)
corner(b, 8)
stroke(b, r.selected and T.accent or T.line, 1)
if mode == "select" then
local box = new("Frame", {
Position = UDim2.fromOffset(10, 13), Size = UDim2.fromOffset(14, 14),
BackgroundColor3 = r.selected and T.accent or T.off, BorderSizePixel = 0,
}, b)
corner(box, 4)
end
makeText({ Position = UDim2.fromOffset(mode == "select" and 34 or 12, 4), Size = UDim2.new(1, -44, 0, 17), Text = r.title,
Font = Enum.Font.GothamMedium, TextSize = 12 }, b)
makeText({ Position = UDim2.fromOffset(mode == "select" and 34 or 12, 21), Size = UDim2.new(1, -44, 0, 14), Text = r.detail,
Font = Enum.Font.RobotoMono, TextSize = 10, TextColor3 = T.dim }, b)
b.MouseButton1Click:Connect(function()
if mode == "select" then
Core.toggleArea(ctl, r.index)
for _, view in ipairs(areaViews) do
view.render()
end
else
teleportTo(r.centroid.x, r.centroid.y + 5, r.centroid.z)
Hub:Notify("Teleport", r.title)
end
end)
end
end
areaViews[#areaViews + 1] = { render = render }
render()
end
-- player features -----------------------------------------------------------------------------------------
local Player = { walk = nil, jump = nil, defWalk = 16, defJump = 50, infinite = nil, afk = nil }
local function readDefaults()
local _, hum = character()
if hum then
Player.defWalk = math.floor(hum.WalkSpeed + 0.5)
Player.defJump = math.floor(hum.JumpPower + 0.5)
end
end
local function applyPlayerOverrides()
local _, hum = character()
if not hum then
return
end
if Player.walk then
hum.WalkSpeed = Player.walk
end
if Player.jump then
hum.UseJumpPower = true
hum.JumpPower = Player.jump
end
end
local function restorePlayer()
local _, hum = character()
if hum then
if Player.walk then
hum.WalkSpeed = Player.defWalk
end
if Player.jump then
hum.JumpPower = Player.defJump
end
end
end
-- main loop -----------------------------------------------------------------------------------------------
local live = {}
local function runLoop()
local lastHaltCount = 0
local lastTarget = "-"
local IDLE = { no_target = true, source_unavailable = true, player_unavailable = true, paused = true, aborted = true }
local function step()
local _, hum = character()
ctl.cfg.select.speed = (CFG.moveTarget == "teleport") and CFG.teleportSpeed or (hum and hum.WalkSpeed or nil)
ctl.cfg.returnInstant = CFG.moveBase == "teleport"
local result, extra = Core.runOnce(ctl)
if result == "place_failed" or (result == "deposited" and extra.kept > 0 and extra.detail) then
print("[kryven] placement refused | " .. tostring(extra.detail) .. " | " .. placeDiagnostics())
end
if ctl.haltCount ~= lastHaltCount then
lastHaltCount = ctl.haltCount
if ctl.haltCount > 0 then
print(string.format("[kryven] halt #%d: %s", ctl.haltCount, tostring(ctl.lastHalt)))
end
end
if type(extra) == "table" and extra.name and extra.pos then
local idx = ctl.areas and ctl.areas.byKey[Core.pointKey(extra.pos)]
lastTarget = extra.name .. (idx and (" (area " .. idx .. ")") or "")
end
local text = describe(result, extra)
if ctl.stopReason then
print("[kryven] stopped: " .. ctl.stopReason)
text = "STOPPED: " .. ctl.stopReason
if live.autoToggle then
live.autoToggle.Set(false)
else
State.enabled = false
end
end
if live.state then
live.state.Set(text)
live.target.Set(lastTarget)
live.counts.Set(string.format("%d / %d / %d / %d", ctl.stolen, ctl.placed, ctl.kept, ctl.sold))
if result == "deposited" then
local function ms(a, b)
return (a ~= 0 and b ~= 0) and string.format("%.0f", (b - a) * 1000) or "?"
end
live.timing.Set(string.format("%s / %s / %s ms", ms(timeline.tTrigger, timeline.tTool),
ms(timeline.tTrigger, timeline.tTp), ms(timeline.tTp, timeline.tPlaced)))
end
end
return result
end
while State.alive do
if not State.enabled then
task.wait(0.2)
else
-- an uncaught error would end this thread silently while the toggle still looks ON
local ok, result = pcall(step)
if not ok then
warn("[kryven] loop error: " .. tostring(result))
if live.state then
live.state.Set("error: " .. tostring(result))
end
task.wait(1)
else
task.wait(IDLE[result] and CFG.idleDelay or CFG.loopDelay)
end
end
end
end
local function buildTabs()
local farm = Hub:AddTab("Farm", "diamond", "Auto steal, pen and areas")
local sSteal = farm:Section("AUTO STEAL", 1)
live.autoToggle = sSteal:Toggle({
name = "Auto Steal", desc = "Steals Exus and brings them to your pen", flag = "autosteal", noSave = true, noReset = false,
callback = function(v)
State.enabled = v
if v then
Core.reset(ctl)
end
end,
})
sSteal:Toggle({
name = "Return and place", desc = "Goes home after each steal and places the Exu", flag = "deposit", default = true,
callback = function(v) ctl.cfg.deposit = v end,
})
sSteal:Dropdown({
name = "If the pen is full", desc = "Sell is permanent", flag = "onfull", options = { "Keep in inventory", "Sell", "Stop" },
default = "Keep in inventory",
callback = function(v)
ctl.cfg.onPlaceFail = (v == "Sell" and "sell") or (v == "Stop" and "stop") or "keep"
end,
})
sSteal:Toggle({
name = "Escape on steal", desc = "Teleports home the instant the steal fires", flag = "escape", default = true,
callback = function(v) ctl.cfg.escapeOnTrigger = v end,
})
sSteal:Slider({
name = "Batch", flag = "batch", min = 1, max = 5, default = 1,
callback = function(v) ctl.cfg.batch = v end,
})
local sMove = farm:Section("MOVEMENT", 1)
sMove:Dropdown({
name = "Go to Exu", flag = "moveTarget", options = { "Teleport", "Walk" }, default = "Teleport",
callback = function(v) CFG.moveTarget = v:lower() end,
})
sMove:Dropdown({
name = "Return to pen", flag = "moveBase", options = { "Teleport", "Walk" }, default = "Teleport",
callback = function(v) CFG.moveBase = v:lower() end,
})
sMove:Dropdown({
name = "Steal method", flag = "hold", options = { "Instant", "Hold" }, default = "Instant",
callback = function(v) CFG.hold = v == "Instant" and "fire" or "hold" end,
})
sMove:Slider({
name = "Teleport settle", flag = "settle", min = 0, max = 400, step = 10, default = 120,
format = function(v) return v .. "ms" end,
callback = function(v) CFG.settleSeconds = v / 1000 end,
})
local sTarget = farm:Section("TARGETING", 2)
sTarget:Dropdown({
name = "Priority", flag = "priority", options = { "Efficiency", "Highest Need", "Nearest" }, default = "Efficiency",
callback = function(v)
ctl.cfg.select.order = (v == "Efficiency" and "efficiency") or (v == "Nearest" and "nearest") or "value"
end,
})
sTarget:Dropdown({
name = "Area order", desc = "With several areas selected", flag = "areaorder",
options = { "Best area first", "Nearest area first", "Off" }, default = "Best area first",
callback = function(v)
ctl.cfg.areaPriority = (v == "Best area first" and "best") or (v == "Nearest area first" and "near") or false
end,
})
sTarget:Slider({
name = "Max range", flag = "range", min = 50, max = 2000, step = 50, default = 2000,
format = function(v) return v >= 2000 and "any" or tostring(v) end,
callback = function(v) ctl.cfg.select.maxRange = v >= 2000 and math.huge or v end,
})
local sAreas = farm:Section("AREAS", 2)
sAreas:Buttons({
name = "Quick select", buttons = {
{ text = "All", callback = function() Core.selectAll(ctl) for _, v in ipairs(areaViews) do v.render() end end },
{ text = "Last", accent = true, callback = function() Core.selectLast(ctl) for _, v in ipairs(areaViews) do v.render() end end },
},
})
areaRows(sAreas:Container("areas"), "select")
sAreas:Buttons({
name = "Forget spawn points", desc = "Use after the map changed", buttons = {
{ text = "Reset", callback = function() Core.clearAreas(ctl) for _, v in ipairs(areaViews) do v.render() end end },
},
})
local sStatus = farm:Section("STATUS", 2)
live.state = sStatus:Info({ name = "State", value = "idle" })
live.target = sStatus:Info({ name = "Last target", value = "-" })
live.counts = sStatus:Info({ name = "Stolen / placed / kept / sold", value = "0 / 0 / 0 / 0" })
live.timing = sStatus:Info({ name = "Trigger>Tool / TP / place", value = "-" })
-- Player -------------------------------------------------------------------------------------------
local player = Hub:AddTab("Player", "dot", "Movement and utility")
local sMoveP = player:Section("MOVEMENT", 1)
sMoveP:Slider({
name = "WalkSpeed", flag = "walkspeed", min = 8, max = 200, default = Player.defWalk,
callback = function(v)
Player.walk = (math.abs(v - Player.defWalk) < 0.5) and nil or v
if not Player.walk then
local _, hum = character()
if hum then hum.WalkSpeed = Player.defWalk end
end
end,
})
sMoveP:Slider({
name = "JumpPower", flag = "jumppower", min = 20, max = 300, default = math.clamp(Player.defJump, 20, 300),
callback = function(v)
Player.jump = (math.abs(v - Player.defJump) < 0.5) and nil or v
if not Player.jump then
local _, hum = character()
if hum then hum.JumpPower = Player.defJump end
end
end,
})
sMoveP:Toggle({
name = "Infinite Jump", flag = "infjump",
callback = function(v)
if v and not Player.infinite then
Player.infinite = UserInputService.JumpRequest:Connect(function()
local _, hum = character()
if hum then
hum:ChangeState(Enum.HumanoidStateType.Jumping)
end
end)
connections[#connections + 1] = Player.infinite
elseif not v and Player.infinite then
Player.infinite:Disconnect()
Player.infinite = nil
end
end,
})
local sMisc = player:Section("MISC", 2)
sMisc:Toggle({
name = "Anti AFK", desc = "Prevents idle kick", flag = "antiafk", default = true,
callback = function(v)
if v and not Player.afk then
Player.afk = lp.Idled:Connect(function()
local vu = game:GetService("VirtualUser")
vu:CaptureController()
vu:ClickButton2(Vector2.new(0, 0))
end)
connections[#connections + 1] = Player.afk
elseif not v and Player.afk then
Player.afk:Disconnect()
Player.afk = nil
end
end,
})
-- Teleport -----------------------------------------------------------------------------------------
local tp = Hub:AddTab("Teleport", "ring", "Pen and farming areas")
local sBase = tp:Section("PEN", 1)
sBase:Buttons({
name = "Teleport to pen", buttons = {
{ text = "Go", accent = true, callback = function()
local base = Source.basePos()
if base then
teleportTo(base.x, base.y + 4, base.z)
Hub:Notify("Teleport", "Pen")
else
Hub:Notify("Teleport", "Pen not found", true)
end
end },
},
})
local sTpAreas = tp:Section("AREAS", 2)
areaRows(sTpAreas:Container("areas"), "teleport")
-- Settings -----------------------------------------------------------------------------------------
local settings = Hub:AddTab("Settings", "bars", "Interface, configs and about")
local sUi = settings:Section("INTERFACE", 1)
sUi:Keybind({
name = "Toggle UI", flag = "togglekey", default = "RightShift", noReset = true,
callback = function(v) Hub.toggleKey = keyCode(v) or Enum.KeyCode.RightShift end,
})
sUi:Slider({
name = "UI scale", flag = "uiscale", min = 60, max = 140, step = 5, default = 100,
format = function(v) return v .. "%" end,
callback = function(v) Hub.userScale.Scale = v / 100 end,
})
sUi:Toggle({
name = "Blur background", flag = "blur", notify = false,
callback = function(v) Hub.blurWanted = v Hub:_applyBlur() end,
})
sUi:Toggle({
name = "Notifications", desc = "Shows a toast when a feature changes", flag = "notifications", default = true, notify = false,
callback = function(v) Hub.notifications = v end,
})
local sCfg = settings:Section("CONFIG", 2)
sCfg:Toggle({ name = "Auto load on join", flag = "autoload", default = true, notify = false })
sCfg:Buttons({ name = "Save current config", buttons = { { text = "Save", callback = function() Hub:SaveConfig() end } } })
sCfg:Buttons({ name = "Load config", buttons = { { text = "Load", callback = function() Hub:LoadConfig(false) end } } })
local sAbout = settings:Section("ABOUT", 2)
sAbout:Info({ name = "Version", value = VERSION })
sAbout:Info({ name = "Game", value = Hub.gameName })
sAbout:Info({ name = "Place", value = tostring(game.PlaceId) })
local executor = "unknown"
if type(identifyexecutor) == "function" then
local ok, name = pcall(identifyexecutor)
if ok and name then
executor = tostring(name)
end
end
sAbout:Info({ name = "Executor", value = executor })
sAbout:Buttons({ name = "Unload hub", desc = "Stops everything and removes the UI", buttons = {
{ text = "Unload", danger = true, callback = function() env.__KryvenUnload() end },
} })
end
-- unload ------------------------------------------------------------------------------------------------------
local function unload()
if not State.alive then
return
end
State.alive, State.enabled = false, false
for _, c in ipairs(connections) do
pcall(function() c:Disconnect() end)
end
connections = {}
pcall(restorePlayer)
if Hub.blur then
pcall(function() Hub.blur:Destroy() end)
end
pcall(function() gui:Destroy() end)
if env.__KryvenUnload == unload then
env.__KryvenUnload = nil
end
end
env.__KryvenUnload = unload
-- boot ----------------------------------------------------------------------------------------------------------
if not mountGui() then
warn("[kryven] could not parent the ScreenGui; nothing started")
unload()
return Core
end
local gameName = "Steal An Exu"
do
local fetched
task.spawn(function()
local ok, info = pcall(MarketplaceService.GetProductInfo, MarketplaceService, game.PlaceId)
fetched = ok and info and info.Name or false
end)
local deadline = os.clock() + 1.5
while fetched == nil and os.clock() < deadline do
task.wait()
end
if type(fetched) == "string" and fetched ~= "" then
gameName = fetched
end
end
Hub.gameName = gameName
runSplash(gameName, {
{ "Initializing", function() readDefaults() end },
{ "Reading game data", function()
local snaps = Source.snapshot()
if snaps then
Core.observe(ctl, snaps)
end
end },
{ "Building interface", function()
Hub:Build(gameName)
buildTabs()
Hub:Select(Hub.tabs[1])
end },
{ "Loading config", function()
Hub:LoadConfig(true)
end },
})
Hub.ready = true
Hub:Open()
Hub:Notify("Kryven", "Loaded - " .. Hub.toggleKey.Name .. " toggles the hub", true)
task.spawn(runLoop)
task.spawn(function()
while State.alive do
applyPlayerOverrides()
local snaps = Source.snapshot()
if snaps then
Core.observe(ctl, snaps)
end
for _, view in ipairs(areaViews) do
view.render()
end
task.wait(1)
end
end)
return Core


