Cool Rarities
Key Required

Cool Rarities

Cool Rarities! (UPDATE!)HippedScriptsHippedScripts 108 views 354 executesUpdated Jul 29, 2026
Unrated
00
Lua Script•3947 lines (67320 chars)
--[[
	SMART RNG V5.1 - ENGINE
	Dies ist LocalScript 1 von 2.

	Empfohlener Ort:
	StarterPlayer > StarterPlayerScripts

	Dieses Script:
	- liest PlayerData
	- erkennt Roll-, Upgrade-, Reset- und Rune-Buttons
	- bewertet wiederholbare und einmalige Upgrades gemeinsam
	- berücksichtigt, dass ungenutzte RP, SM, PP und Clovers selbst Wert besitzen
	- führt die beste gefundene Aktion aus
	- stellt eine Bindable-Schnittstelle für das zweite GUI-LocalScript bereit

	Kein Server-Hopping.
	Keine Robux-Käufe.
]]

if not game:IsLoaded() then
	game.Loaded:Wait()
end

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local LP = Players.LocalPlayer
local PlayerScripts = LP:WaitForChild("PlayerScripts")
local PlayerData = LP:WaitForChild("PlayerData")

--====================================================
-- BRIDGE ZUM GUI-SCRIPT
--====================================================

local function ensureChild(parent, className, name)
	local existing = parent:FindFirstChild(name)

	if existing and existing.ClassName ~= className then
		existing:Destroy()
		existing = nil
	end

	if not existing then
		existing = Instance.new(className)
		existing.Name = name
		existing.Parent = parent
	end

	return existing
end

local Bridge = PlayerScripts:FindFirstChild("_SmartRNGBridge")

if not Bridge then
	Bridge = Instance.new("Folder")
	Bridge.Name = "_SmartRNGBridge"
	Bridge.Parent = PlayerScripts
end

local CommandEvent =
	ensureChild(Bridge, "BindableEvent", "Command")

local StateChangedEvent =
	ensureChild(Bridge, "BindableEvent", "StateChanged")

local GetSnapshotFunction =
	ensureChild(Bridge, "BindableFunction", "GetSnapshot")

local EngineReadyValue =
	ensureChild(Bridge, "BoolValue", "EngineReady")

local EngineErrorValue =
	ensureChild(Bridge, "StringValue", "EngineError")

EngineReadyValue.Value = false
EngineErrorValue.Value = ""

--====================================================
-- ENGINE-STATE
--====================================================

local E = {
	Config = {
		Master = false,
		Strategy = "Balanced",

		AutoRoll = true,
		AutoRaritySpend = true,
		AutoRebirth = true,
		AutoRPUpgrades = true,
		AutoSuperMultiplier = true,
		AutoSMUpgrades = true,
		AutoPrestige = true,
		AutoPPUpgrades = true,
		AutoAscension = true,
		AutoCloverUpgrades = true,

		AutoCloverRunes = false,
		AutoPrestigeRunes = false,
		AutoMultiplierRunes = false,
		AutoPlantRunes = false,

		ButtonNoclip = true,
		AntiKnockback = true,

		-- Der ausgewählte Button wird kurz unter den Spieler gesetzt,
		-- aktiv gepulst und danach sicher neben dem Spieler geparkt.
		StageButtons = true,
		ReliableTouch = true,

		SpendBeforeReset = true,
		NormalRaritySpend = true,
	},

	State = {
		Ready = false,
		Busy = false,
		RollBusy = false,
		Dirty = true,

		LastAction = "Engine startet",
		Plan = "Warte auf Initialisierung",
		Error = nil,

		DecisionType = "Keine",
		DecisionScore = 0,
		TopDecisions = {},

		ButtonCount = 0,
		RepeatableCount = 0,
		GhostCount = 0,

		TouchMode = "Nicht geprüft",
		LastTouch = {},

		ActiveStagedPart = nil,
		StagedButtonName = "Keiner",
		TouchConfirmed = false,

		PreResetName = nil,
		PreResetSpends = 0,
	},

	Timing = {
		RollInterval = 0.10,
		ThinkInterval = 0.18,
		ActionSettle = 0.13,
		TouchCooldown = 0.10,
		NotifyInterval = 0.15,
	},

	Limits = {
		MaxPreResetSpends = 2,
		RarityReserveFraction = 0.68,
		NearMilestoneFraction = 0.15,
	},

	BankElasticity = {
		RebirthPoints = 0.55,
		UpgradesMultiMulti = 0.85,
		PrestigePoints = 0.50,
		Clovers = 0.32,
	},

	Stats = {},
	Bools = {},
	Buttons = {},
	ByCategory = {},
	ButtonConnections = {},
	GhostParts = {},
	GhostConnections = {},

	ButtonStageState = {},

	LastNotify = 0,
}

local OPTION_KEYS = {
	"AutoRoll",
	"AutoRaritySpend",
	"AutoRebirth",
	"AutoRPUpgrades",
	"AutoSuperMultiplier",
	"AutoSMUpgrades",
	"AutoPrestige",
	"AutoPPUpgrades",
	"AutoAscension",
	"AutoCloverUpgrades",
	"AutoCloverRunes",
	"AutoPrestigeRunes",
	"AutoMultiplierRunes",
	"AutoPlantRunes",
	"ButtonNoclip",
	"AntiKnockback",
	"StageButtons",
	"ReliableTouch",
}

--====================================================
-- BIG-NUMBER-NAMESPACE
--====================================================

local N = {}
N.NEG_INF = -math.huge
N.ZERO = {
	Log10 = N.NEG_INF,
	Number = 0,
	Raw = "0",
}

N.Suffix = {
	[""] = 0,
	K = 3,
	M = 6,
	B = 9,
	T = 12,

	Qd = 15,
	Qn = 18,
	Sx = 21,
	Sp = 24,
	Oc = 27,
	No = 30,

	De = 33,
	UDe = 36,
	DDe = 39,
	TDe = 42,
	QdDe = 45,
	QnDe = 48,
	SxDe = 51,
	SpDe = 54,
	OcDe = 57,
	NoDe = 60,

	Vg = 63,
	UVg = 66,
	DVg = 69,
	TVg = 72,
	QdVg = 75,
	QnVg = 78,
	SxVg = 81,
	SpVg = 84,
	OcVg = 87,
	NoVg = 90,

	Tg = 93,
	UTg = 96,
	DTg = 99,
	TTg = 102,
	QdTg = 105,
	QnTg = 108,
	SxTg = 111,
	SpTg = 114,
	OcTg = 117,
	NoTg = 120,

	qg = 123,
	Uqg = 126,
	Dqg = 129,
	Tqg = 132,
	Qdqg = 135,
	Qnqg = 138,
	Sxqg = 141,
	Spqg = 144,
	Ocqg = 147,
	Noqg = 150,

	Qg = 153,
	UQg = 156,
	DQg = 159,
	TQg = 162,
	QdQg = 165,
	QnQg = 168,
	SxQg = 171,
	SpQg = 174,
	OcQg = 177,
	NoQg = 180,

	sg = 183,
	Usg = 186,
	Dsg = 189,
	Tsg = 192,
	Qdsg = 195,
	Qnsg = 198,
	Sxsg = 201,
	Spsg = 204,
	Ocsg = 207,
	Nosg = 210,

	Sg = 213,
	USg = 216,
	DSg = 219,
	TSg = 222,
	QdSg = 225,
	QnSg = 228,
	SxSg = 231,
	SpSg = 234,
	OcSg = 237,
	NoSg = 240,

	Og = 243,
	UOg = 246,
	DOg = 249,
	TOg = 252,
	QdOg = 255,
	QnOg = 258,
	SxOg = 261,
	SpOg = 264,
	OcOg = 267,
	NoOg = 270,

	Ng = 273,
	UNg = 276,
	DNg = 279,
	TNg = 282,
	QdNg = 285,
	QnNg = 288,
	SxNg = 291,
	SpNg = 294,
	OcNg = 297,
	NoNg = 300,

	Ce = 303,
	UCe = 306,
	DCe = 309,
	TCe = 312,
	QdCe = 315,
	QnCe = 318,
	SxCe = 321,
	SpCe = 324,
	OcCe = 327,
	NoCe = 330,
}

function N.make(log10Value, number, raw)
	return {
		Log10 = log10Value,
		Number = number,
		Raw = raw,
	}
end

function N.fromNumber(value, raw)
	value = tonumber(value)

	if not value or value <= 0 then
		return N.ZERO
	end

	return N.make(
		math.log10(value),
		value,
		raw or tostring(value)
	)
end

function N.clean(text)
	text = tostring(text or "")
	text = text:gsub("<[^>]->", "")
	text = text:gsub(",", "")
	text = text:gsub("^%s+", "")
	text = text:gsub("%s+$", "")
	return text
end

function N.decode(value)
	value = tostring(value or "0;0")

	local layerText, magnitudeText =
		value:match(
			"^%s*([%-%d%.]+)%s*;%s*([%-%d%.eE+]+)%s*$"
		)

	if not layerText then
		return N.fromNumber(tonumber(value), value)
	end

	local layer = tonumber(layerText) or 0
	local magnitude = tonumber(magnitudeText) or 0

	if magnitude <= 0 then
		return N.ZERO
	end

	if layer <= 0 then
		return N.fromNumber(magnitude, value)
	end

	if layer == 1 then
		local number = nil

		if magnitude <= 308 then
			number = 10 ^ magnitude
		end

		return N.make(magnitude, number, value)
	end

	return N.make(
		layer * 1e12 + magnitude,
		nil,
		value
	)
end

function N.parseDisplay(text)
	text = N.clean(text)

	local scientific =
		text:match("([%d%.]+[eE][%+%-]?%d+)")

	if scientific then
		return N.fromNumber(
			tonumber(scientific),
			scientific
		)
	end

	local numberText, suffix =
		text:match(
			"([%d]+%.?[%d]*)%s*([%a]+)"
		)

	if not numberText then
		numberText =
			text:match("([%d]+%.?[%d]*)")

		suffix = ""
	end

	local number = tonumber(numberText)

	if not number then
		return N.ZERO
	end

	suffix = suffix or ""

	local exponent = N.Suffix[suffix] or 0
	local log10Value =
		math.log10(math.max(number, 1e-300))
		+ exponent

	local numeric = nil

	if log10Value <= 308 then
		numeric = number * 10 ^ exponent
	end

	return N.make(
		log10Value,
		numeric,
		numberText .. suffix
	)
end

function N.ge(a, b)
	return a.Log10 >= b.Log10 - 1e-10
end

function N.gt(a, b)
	return a.Log10 > b.Log10 + 1e-10
end

function N.add(a, b)
	if a.Log10 == N.NEG_INF then
		return b
	end

	if b.Log10 == N.NEG_INF then
		return a
	end

	local high = math.max(a.Log10, b.Log10)
	local low = math.min(a.Log10, b.Log10)

	local resultLog =
		high
		+ math.log10(
			1 + 10 ^ math.max(-300, low - high)
		)

	local number = nil

	if resultLog <= 308 then
		number = 10 ^ resultLog
	end

	return N.make(resultLog, number, "sum")
end

function N.scale(a, multiplier)
	multiplier = tonumber(multiplier) or 0

	if multiplier <= 0
		or a.Log10 == N.NEG_INF
	then
		return N.ZERO
	end

	return N.make(
		a.Log10 + math.log10(multiplier),
		a.Number and a.Number * multiplier or nil,
		"scaled"
	)
end

function N.ratioLog(a, b)
	if a.Log10 == N.NEG_INF then
		return N.NEG_INF
	end

	if b.Log10 == N.NEG_INF then
		return math.huge
	end

	return a.Log10 - b.Log10
end

function N.costFraction(cost, balance)
	if not cost
		or cost.Log10 == N.NEG_INF
		or balance.Log10 == N.NEG_INF
	then
		return 1
	end

	local difference =
		cost.Log10 - balance.Log10

	if difference >= 0 then
		return 1
	end

	if difference < -12 then
		return 0
	end

	return 10 ^ difference
end

function N.format(value)
	if not value
		or value.Log10 == N.NEG_INF
	then
		return "0"
	end

	if value.Number and value.Number < 1000 then
		if value.Number >= 100 then
			return string.format("%.0f", value.Number)
		elseif value.Number >= 10 then
			return string.format("%.1f", value.Number)
		else
			return string.format("%.2f", value.Number)
		end
	end

	local ordered = {}

	for suffix, exponent in pairs(N.Suffix) do
		table.insert(ordered, {
			Suffix = suffix,
			Exponent = exponent,
		})
	end

	table.sort(ordered, function(a, b)
		return a.Exponent < b.Exponent
	end)

	local selected = ordered[1]

	for _, entry in ipairs(ordered) do
		if entry.Exponent <= value.Log10 then
			selected = entry
		else
			break
		end
	end

	local displayed =
		10 ^ (value.Log10 - selected.Exponent)

	if displayed >= 100 then
		return string.format(
			"%.0f%s",
			displayed,
			selected.Suffix
		)
	elseif displayed >= 10 then
		return string.format(
			"%.1f%s",
			displayed,
			selected.Suffix
		)
	else
		return string.format(
			"%.2f%s",
			displayed,
			selected.Suffix
		)
	end
end

--====================================================
-- STATUS UND SNAPSHOT
--====================================================

function E.markDirty()
	E.State.Dirty = true
end

function E.notify(force)
	local now = os.clock()

	if not force
		and now - E.LastNotify
			< E.Timing.NotifyInterval
	then
		return
	end

	E.LastNotify = now
	StateChangedEvent:Fire()
end

function E.getStat(name)
	return E.Stats[name] or N.ZERO
end

function E.small(name)
	return E.getStat(name).Number or 0
end

function E.snapshot()
	local options = {}

	for _, key in ipairs(OPTION_KEYS) do
		options[key] = E.Config[key]
	end

	local decisions = {}

	for index, candidate in ipairs(
		E.State.TopDecisions
	) do
		if index > 5 then
			break
		end

		decisions[index] = {
			Name = candidate.Name,
			Type = candidate.Type,
			Score = candidate.Score,
			Details = candidate.Details,
		}
	end

	return {
		Ready = E.State.Ready,
		Master = E.Config.Master,
		Strategy = E.Config.Strategy,

		Options = options,

		LastAction = E.State.LastAction,
		Plan = E.State.Plan,
		Error = E.State.Error,

		DecisionType = E.State.DecisionType,
		DecisionScore = E.State.DecisionScore,
		Decisions = decisions,

		ButtonCount = E.State.ButtonCount,
		RepeatableCount =
			E.State.RepeatableCount,
		GhostCount = E.State.GhostCount,
		TouchMode = E.State.TouchMode,
		StagedButton = E.State.StagedButtonName,
		TouchConfirmed = E.State.TouchConfirmed,

		Stats = {
			Rarity = N.format(
				E.getStat("Rarity")
			),
			BestRarity = N.format(
				E.getStat("BestRarity")
			),
			Luck = N.format(
				E.getStat("LuckMulti")
			),
			Multiplier = N.format(
				E.getStat("MultiOriginal")
			),
			RP = N.format(
				E.getStat("RebirthPoints")
			),
			RPM = N.format(
				E.getStat("RPM")
			),
			SM = N.format(
				E.getStat("UpgradesMultiMulti")
			),
			PP = N.format(
				E.getStat("PrestigePoints")
			),
			Clovers = N.format(
				E.getStat("Clovers")
			),
			Ascension = N.format(
				E.getStat("Ascension")
			),
		},
	}
end

GetSnapshotFunction.OnInvoke = function()
	return E.snapshot()
end

--====================================================
-- PLAYERDATA-CACHE
--====================================================

local TRACKED_STATS = {
	"Rarity",
	"BestRarity",
	"LuckMulti",
	"RebirthPoints",
	"RPM",
	"MultiOriginal",
	"Lesschancelower",
	"UpgradesCashMulti",
	"UpgradesMultiMulti",
	"PrestigePoints",
	"PPM",
	"PrTime",
	"PRPM",
	"PSM",
	"Clovers",
	"CM",
	"CLM",
	"CRPM",
	"CLSM",
	"CLPP",
	"Ascension",
	"RuneBulk",
}

local TRACKED_BOOLS = {
	"IsRollin",
	"ToggleAutoRoll",
	"Upgrade1Used",
	"Upgrade2Used",
	"Upgrade3Used",
	"Upgrade4Used",
}

function E.refreshStat(name)
	local object = PlayerData:FindFirstChild(name)

	if not object then
		E.Stats[name] = N.ZERO
		return
	end

	if object:IsA("StringValue") then
		E.Stats[name] = N.decode(object.Value)
	elseif object:IsA("NumberValue")
		or object:IsA("IntValue")
	then
		E.Stats[name] =
			N.fromNumber(object.Value)
	else
		E.Stats[name] = N.ZERO
	end
end

function E.refreshBool(name)
	local object = PlayerData:FindFirstChild(name)

	E.Bools[name] =
		object
		and object:IsA("BoolValue")
		and object.Value
		or false
end

function E.watchStats()
	for _, name in ipairs(TRACKED_STATS) do
		E.refreshStat(name)

		local object =
			PlayerData:FindFirstChild(name)

		if object then
			object.Changed:Connect(function()
				E.refreshStat(name)
				E.markDirty()
			end)
		end
	end

	for _, name in ipairs(TRACKED_BOOLS) do
		E.refreshBool(name)

		local object =
			PlayerData:FindFirstChild(name)

		if object then
			object.Changed:Connect(function()
				E.refreshBool(name)
				E.markDirty()
			end)
		end
	end
end

--====================================================
-- BUTTON-ERKENNUNG
--====================================================

local ROOT_NAMES = {
	"Buttons",
	"Roll System",
	"asecnd",
	"runez",
	"runezz",
	"runezzz",
	"runeth",
}

function E.readString(parent, name)
	local object =
		parent and parent:FindFirstChild(name)

	if object and object:IsA("StringValue") then
		return object.Value, object
	end

	return nil, nil
end

function E.label(part, childName)
	local child = part:FindFirstChild(childName)

	return child
		and child:FindFirstChildWhichIsA(
			"TextLabel",
			true
		)
		or nil
end

function E.labelText(label)
	return label and N.clean(label.Text) or ""
end

function E.isTouchPart(instance)
	return instance:IsA("BasePart")
		and instance:FindFirstChildOfClass(
			"TouchTransmitter"
		) ~= nil
end

function E.categorize(meta)
	if meta.Path
		== "Workspace.Roll System.RollRarity"
	then
		return "Roll"
	end

	if meta.Path
		== "Workspace.asecnd.RollRarity"
	then
		return "Ascension"
	end

	if meta.Path
		== "Workspace.runez.RollRarity"
	then
		return "CloverRune"
	end

	if meta.Path
		== "Workspace.runezz.RollRarity"
	then
		return "PrestigeRune"
	end

	if meta.Path
		== "Workspace.runezzz.RollRarity"
	then
		return "MultiplierRune"
	end

	if meta.Path
		== "Workspace.runeth.RollRarity"
	then
		return "PlantRune"
	end

	if meta.Name == "Rebirth Get" then
		return "Rebirth"
	end

	if meta.Name == "Prestige Get" then
		return "Prestige"
	end

	if meta.Name == "Super Multi Get" then
		return "SuperReset"
	end

	if meta.CostStat == "Rarity"
		and meta.GiveStat == "MultiOriginal"
	then
		return "RarityMultiplier"
	end

	if meta.CostStat == "RebirthPoints" then
		return "RPUpgrade"
	end

	if meta.CostStat == "PrestigePoints" then
		return "PPUpgrade"
	end

	if meta.CostStat
		== "UpgradesMultiMulti"
	then
		return "SMUpgrade"
	end

	if meta.CostStat == "Clovers" then
		return "CloverUpgrade"
	end

	return "Other"
end

function E.isBought(meta)
	return string.find(
		string.lower(E.labelText(meta.Up2)),
		"bought",
		1,
		true
	) ~= nil
end

function E.determineRepeatable(meta)
	if E.isBought(meta) then
		return false
	end

	local up1 =
		string.lower(E.labelText(meta.Up1))

	if meta.Category == "RPUpgrade" then
		if meta.GiveStat == "RPM" then
			return true
		end

		if meta.GiveStat == "LuckMulti"
			and string.find(
				up1,
				"x luck",
				1,
				true
			)
			and not string.find(
				up1,
				"every rebirth",
				1,
				true
			)
			and not string.find(
				up1,
				"multiplier buttons",
				1,
				true
			)
		then
			return true
		end
	end

	if meta.Category == "SMUpgrade"
		or meta.Category == "PPUpgrade"
		or meta.Category == "CloverUpgrade"
	then
		return true
	end

	return false
end

function E.unregisterButton(part)
	local meta = E.Buttons[part]

	if not meta then
		return
	end

	E.Buttons[part] = nil

	local category =
		E.ByCategory[meta.Category]

	if category then
		for index = #category, 1, -1 do
			if category[index] == meta then
				table.remove(category, index)
				break
			end
		end
	end

	local connections =
		E.ButtonConnections[part]

	if connections then
		for _, connection in ipairs(
			connections
		) do
			connection:Disconnect()
		end
	end

	E.ButtonConnections[part] = nil
	E.recountButtons()
	E.markDirty()
end

function E.registerButton(part)
	if not E.isTouchPart(part)
		or E.Buttons[part]
	then
		return
	end

	local scriptFolder =
		part:FindFirstChild("Script")

	local costStat, costStatObject =
		E.readString(
			scriptFolder,
			"Whatstatcost"
		)

	local giveStat, giveStatObject =
		E.readString(
			scriptFolder,
			"Whatstatgives"
		)

	local baseCost, baseCostObject =
		E.readString(
			scriptFolder,
			"Cost"
		)

	local baseGive, baseGiveObject =
		E.readString(
			scriptFolder,
			"Gives"
		)

	local required, requiredObject =
		E.readString(
			scriptFolder,
			"Requiredrarity"
		)

	local formula, formulaObject =
		E.readString(
			scriptFolder,
			"Costformula"
		)

	if not formula then
		formula, formulaObject =
			E.readString(
				scriptFolder,
				"Costformulas"
			)
	end

	local meta = {
		Part = part,
		Path = part:GetFullName(),
		Name = part.Name,

		CostStat = costStat,
		GiveStat = giveStat,
		BaseCostRaw = baseCost,
		BaseGiveRaw = baseGive,
		RequiredRaw = required,
		CostFormula = formula,

		Up1 = E.label(part, "Up1"),
		Up2 = E.label(part, "Up2"),
		Up3 = E.label(part, "Up3"),

		ValueObjects = {
			costStatObject,
			giveStatObject,
			baseCostObject,
			baseGiveObject,
			requiredObject,
			formulaObject,
		},
	}

	meta.Category = E.categorize(meta)
	meta.Repeatable =
		E.determineRepeatable(meta)

	E.Buttons[part] = meta

	E.ByCategory[meta.Category] =
		E.ByCategory[meta.Category] or {}

	table.insert(
		E.ByCategory[meta.Category],
		meta
	)

	local connections = {}

	local function changed()
		meta.Repeatable =
			E.determineRepeatable(meta)

		E.recountButtons()
		E.markDirty()
	end

	for _, label in ipairs({
		meta.Up1,
		meta.Up2,
		meta.Up3,
	}) do
		if label then
			table.insert(
				connections,
				label:GetPropertyChangedSignal(
					"Text"
				):Connect(changed)
			)
		end
	end

	for _, object in ipairs(
		meta.ValueObjects
	) do
		if object then
			table.insert(
				connections,
				object.Changed:Connect(changed)
			)
		end
	end

	table.insert(
		connections,
		part.AncestryChanged:Connect(
			function()
				if not part:IsDescendantOf(
					workspace
				) then
					E.unregisterButton(part)
				end
			end
		)
	)

	E.ButtonConnections[part] = connections
	E.recountButtons()
	E.markDirty()
end

function E.recountButtons()
	local count = 0
	local repeatable = 0

	for _, meta in pairs(E.Buttons) do
		count += 1

		if meta.Repeatable then
			repeatable += 1
		end
	end

	E.State.ButtonCount = count
	E.State.RepeatableCount = repeatable
end

function E.scanButtons()
	for _, rootName in ipairs(ROOT_NAMES) do
		local root =
			workspace:FindFirstChild(rootName)

		if root then
			for _, descendant in ipairs(
				root:GetDescendants()
			) do
				if E.isTouchPart(descendant) then
					E.registerButton(descendant)
				end
			end
		end
	end

	E.recountButtons()
end

function E.category(name)
	return E.ByCategory[name] or {}
end

function E.single(name)
	for _, meta in ipairs(E.category(name)) do
		if meta.Part and meta.Part.Parent then
			return meta
		end
	end

	return nil
end

function E.currentCost(meta)
	if E.isBought(meta) then
		return nil
	end

	if meta.Category == "RPUpgrade"
		or meta.Category == "PPUpgrade"
		or meta.Category == "SMUpgrade"
		or meta.Category == "CloverUpgrade"
	then
		local displayed =
			N.parseDisplay(
				E.labelText(meta.Up2)
			)

		if displayed.Log10 ~= N.NEG_INF then
			return displayed
		end
	end

	if meta.BaseCostRaw then
		return N.decode(meta.BaseCostRaw)
	end

	return N.ZERO
end

function E.gain(meta)
	if meta.Category == "Rebirth"
		or meta.Category == "Prestige"
		or meta.Category == "SuperReset"
		or meta.Category == "Ascension"
	then
		return N.parseDisplay(
			E.labelText(meta.Up2)
		)
	end

	if meta.BaseGiveRaw then
		if string.sub(meta.BaseGiveRaw, 1, 1)
			== "*"
		then
			return N.fromNumber(
				tonumber(
					string.sub(
						meta.BaseGiveRaw,
						2
					)
				)
			)
		end

		return N.decode(meta.BaseGiveRaw)
	end

	return N.ZERO
end

function E.requiredRarity(meta)
	if meta.Category == "Ascension" then
		return 250
	end

	if not meta.RequiredRaw then
		return 0
	end

	return N.decode(meta.RequiredRaw).Number or 0
end

--====================================================
-- BUTTON-NOCLIP UND ANTI-KNOCKBACK
--====================================================

local GHOST_ROOTS = {
	Buttons = true,
	["Roll System"] = true,
	asecnd = true,
	runez = true,
	runezz = true,
	runezzz = true,
	runeth = true,
}

function E.insideGhostRoot(instance)
	local current = instance

	while current and current ~= workspace do
		if current.Parent == workspace
			and GHOST_ROOTS[current.Name]
		then
			return true
		end

		current = current.Parent
	end

	return false
end

function E.registerGhostPart(part)
	if E.GhostParts[part]
		or not part:IsA("BasePart")
		or not E.insideGhostRoot(part)
	then
		return
	end

	-- Nur Touch-Parts durchlässig lassen.
	if not part:FindFirstChildOfClass(
		"TouchTransmitter"
	) then
		return
	end

	E.GhostParts[part] = {
		Original = part.CanCollide,
	}

	local connections = {}

	table.insert(
		connections,
		part:GetPropertyChangedSignal(
			"CanCollide"
		):Connect(function()
			if E.Config.ButtonNoclip
				and part.Parent
				and part.CanCollide
			then
				part.CanCollide = false
			end
		end)
	)

	table.insert(
		connections,
		part.AncestryChanged:Connect(
			function()
				if not part:IsDescendantOf(
					workspace
				) then
					E.GhostParts[part] = nil
				end
			end
		)
	)

	E.GhostConnections[part] =
		connections

	if E.Config.ButtonNoclip then
		part.CanCollide = false
	end

	E.recountGhosts()
end

function E.recountGhosts()
	local count = 0

	for part in pairs(E.GhostParts) do
		if part and part.Parent then
			count += 1
		end
	end

	E.State.GhostCount = count
end

function E.applyGhostSetting()
	for part, data in pairs(E.GhostParts) do
		if part and part.Parent then
			part.CanCollide =
				E.Config.ButtonNoclip
				and false
				or data.Original
		end
	end
end

function E.scanGhosts()
	for rootName in pairs(GHOST_ROOTS) do
		local root =
			workspace:FindFirstChild(rootName)

		if root then
			for _, descendant in ipairs(
				root:GetDescendants()
			) do
				E.registerGhostPart(descendant)
			end
		end
	end

	E.recountGhosts()
end

RunService.Heartbeat:Connect(function()
	if not E.Config.AntiKnockback then
		return
	end

	local character = LP.Character
	local root = character
		and character:FindFirstChild(
			"HumanoidRootPart"
		)

	local humanoid = character
		and character:FindFirstChildOfClass(
			"Humanoid"
		)

	if not root
		or not humanoid
		or humanoid.Health <= 0
	then
		return
	end

	if humanoid.PlatformStand then
		humanoid.PlatformStand = false
	end

	local velocity =
		root.AssemblyLinearVelocity

	local horizontal = Vector3.new(
		velocity.X,
		0,
		velocity.Z
	)

	local maximumHorizontal =
		math.max(
			34,
			humanoid.WalkSpeed * 2.2
		)

	if horizontal.Magnitude
		> maximumHorizontal
	then
		local intended =
			humanoid.MoveDirection
			* humanoid.WalkSpeed

		root.AssemblyLinearVelocity =
			Vector3.new(
				intended.X,
				math.clamp(
					velocity.Y,
					-120,
					55
				),
				intended.Z
			)
	elseif velocity.Y > 55 then
		root.AssemblyLinearVelocity =
			Vector3.new(
				velocity.X,
				55,
				velocity.Z
			)
	end

	if root.AssemblyAngularVelocity.Magnitude
		> 18
	then
		root.AssemblyAngularVelocity =
			Vector3.zero
	end
end)

--====================================================
-- BUTTON-STAGING UND SICHERES PARKEN
--====================================================

function E.buttonRotation(part)
	return part.CFrame - part.Position
end

function E.rememberButtonStageState(part)
	local data = E.ButtonStageState[part]

	if data then
		return data
	end

	data = {
		OriginalCFrame = part.CFrame,
		OriginalCanCollide = part.CanCollide,
		OriginalCanTouch = part.CanTouch,
	}

	E.ButtonStageState[part] = data
	return data
end

function E.setButtonCFrame(part, targetCFrame)
	if not part or not part.Parent then
		return false
	end

	local success = pcall(function()
		part.AssemblyLinearVelocity = Vector3.zero
		part.AssemblyAngularVelocity = Vector3.zero
		part.CanCollide = false
		part.CanTouch = true
		part.CFrame = targetCFrame
	end)

	return success
end

function E.parkButton(part)
	if not part or not part.Parent then
		return
	end

	local root = E.rootPart()

	if not root then
		return
	end

	E.rememberButtonStageState(part)

	local sideDistance =
		root.Size.X * 0.5
		+ part.Size.X * 0.5
		+ 4

	local verticalOffset =
		-(root.Size.Y * 0.5 + part.Size.Y * 0.5)

	local parkingCFrame =
		root.CFrame
		* CFrame.new(
			sideDistance,
			verticalOffset,
			0
		)
		* E.buttonRotation(part)

	E.setButtonCFrame(part, parkingCFrame)

	if E.State.ActiveStagedPart == part then
		E.State.ActiveStagedPart = nil
	end

	E.State.StagedButtonName = "Keiner"
end

function E.parkActiveButton(exceptPart)
	local active = E.State.ActiveStagedPart

	if active and active ~= exceptPart then
		E.parkButton(active)
	end
end

function E.stageButton(part)
	if not E.Config.StageButtons
		or not part
		or not part.Parent
	then
		return false
	end

	local root = E.rootPart()

	if not root then
		return false
	end

	E.parkActiveButton(part)
	E.rememberButtonStageState(part)

	-- Der Button liegt leicht in den Füßen. Dadurch besteht kurz eine
	-- echte Überlappung, während firetouchinterest zusätzlich aktiv pulst.
	local verticalOffset =
		-(root.Size.Y * 0.5 + part.Size.Y * 0.5 - 0.35)

	local activeCFrame =
		root.CFrame
		* CFrame.new(0, verticalOffset, 0)
		* E.buttonRotation(part)

	local success = E.setButtonCFrame(
		part,
		activeCFrame
	)

	if success then
		E.State.ActiveStagedPart = part
		E.State.StagedButtonName = part.Name
	end

	return success
end

function E.restoreStagedButtons()
	E.parkActiveButton(nil)

	for part, data in pairs(E.ButtonStageState) do
		if part and part.Parent then
			pcall(function()
				part.CFrame = data.OriginalCFrame
				part.CanCollide = data.OriginalCanCollide
				part.CanTouch = data.OriginalCanTouch
			end)
		end
	end

	E.State.ActiveStagedPart = nil
	E.State.StagedButtonName = "Keiner"
end

function E.touchSources()
	local character = LP.Character

	if not character then
		return {}
	end

	local sources = {}
	local names = {
		"HumanoidRootPart",
		"LowerTorso",
		"UpperTorso",
		"Torso",
		"LeftFoot",
		"RightFoot",
		"Left Leg",
		"Right Leg",
	}

	local seen = {}

	for _, name in ipairs(names) do
		local part = character:FindFirstChild(name)

		if part
			and part:IsA("BasePart")
			and not seen[part]
		then
			seen[part] = true
			table.insert(sources, part)
		end
	end

	return sources
end

function E.buttonFingerprint(meta)
	if not meta then
		return tostring(E.Bools.IsRollin)
	end

	local values = {
		E.labelText(meta.Up1),
		E.labelText(meta.Up2),
		E.labelText(meta.Up3),
		tostring(E.Bools.IsRollin),
	}

	for _, statName in ipairs({
		meta.CostStat,
		meta.GiveStat,
		"Rarity",
		"RebirthPoints",
		"UpgradesMultiMulti",
		"PrestigePoints",
		"Clovers",
	}) do
		if statName then
			local object = PlayerData:FindFirstChild(statName)

			if object and object:IsA("ValueBase") then
				table.insert(values, tostring(object.Value))
			end
		end
	end

	return table.concat(values, "|")
end

function E.forceTouchPulse(source, buttonPart)
	-- Zuerst ein künstliches Touch-Ende senden. Das ist wichtig, wenn der
	-- Spieler oder Button schon länger still ineinander steht.
	E.FireTouch(source, buttonPart, 1)
	task.wait()
	E.FireTouch(source, buttonPart, 0)
	task.wait(0.025)
	E.FireTouch(source, buttonPart, 1)
end

--====================================================
-- TOUCH-AUSFÜHRUNG
--====================================================

function E.findGlobalFunction(names)
	local environments = {
		_G,
	}

	if type(getgenv) == "function" then
		local success, environment =
			pcall(getgenv)

		if success
			and type(environment) == "table"
		then
			table.insert(
				environments,
				environment
			)
		end
	end

	if type(getfenv) == "function" then
		local success, environment =
			pcall(getfenv)

		if success
			and type(environment) == "table"
		then
			table.insert(
				environments,
				environment
			)
		end
	end

	for _, environment in ipairs(
		environments
	) do
		for _, name in ipairs(names) do
			local value =
				rawget(environment, name)

			if type(value) == "function" then
				return value
			end
		end
	end

	return nil
end

E.FireTouch =
	E.findGlobalFunction({
		"firetouchinterest",
	})

function E.rootPart()
	local character = LP.Character

	return character
		and character:FindFirstChild(
			"HumanoidRootPart"
		)
end

function E.touch(part, meta)
	if not part or not part.Parent then
		return false
	end

	local root = E.rootPart()

	if not root then
		E.State.Error = "HumanoidRootPart fehlt"
		return false
	end

	if not E.FireTouch then
		E.State.TouchMode = "firetouchinterest fehlt"
		E.State.Error =
			"Die Ausführungsumgebung stellt "
			.. "firetouchinterest nicht bereit."

		EngineErrorValue.Value = E.State.Error
		return false
	end

	E.stageButton(part)
	task.wait(0.02)

	local before = E.buttonFingerprint(meta)
	local sources = E.touchSources()

	if #sources == 0 then
		E.State.Error = "Keine Character-Touchparts gefunden"
		E.parkButton(part)
		return false
	end

	local sent = false
	local confirmed = false
	local attempts = E.Config.ReliableTouch and 2 or 1

	for attempt = 1, attempts do
		local source = sources[
			math.min(attempt, #sources)
		]

		local success, result = pcall(function()
			E.forceTouchPulse(source, part)
		end)

		if success then
			sent = true
		else
			E.State.Error = tostring(result)
			EngineErrorValue.Value = E.State.Error
		end

		task.wait(0.07)

		local after = E.buttonFingerprint(meta)

		if after ~= before then
			confirmed = true
			break
		end
	end

	-- Nie einen alten Button im Spieler liegen lassen. Er wird nach jedem
	-- einzelnen Kauf oder Roll seitlich außerhalb der Hitbox geparkt.
	E.parkButton(part)

	E.State.TouchConfirmed = confirmed
	E.State.TouchMode = confirmed
		and "Puls-Touch bestätigt"
		or "Puls-Touch gesendet"

	return sent
end

function E.queue(meta, label)
	if E.State.Busy
		or not meta
		or not meta.Part
		or not meta.Part.Parent
	then
		return false
	end

	local now = os.clock()
	local previous =
		E.State.LastTouch[meta.Part] or 0

	if now - previous
		< E.Timing.TouchCooldown
	then
		return false
	end

	E.State.LastTouch[meta.Part] = now
	E.State.Busy = true
	E.State.LastAction = label
	E.notify(true)

	task.spawn(function()
		local success = E.touch(meta.Part, meta)

		if not success then
			E.State.LastAction =
				"Fehler: " .. label
		end

		task.wait(E.Timing.ActionSettle)

		E.State.Busy = false
		E.markDirty()
		E.notify(true)
	end)

	return true
end

function E.rollTouch()
	if E.State.RollBusy
		or E.Bools.IsRollin
	then
		return
	end

	local roll = E.single("Roll")

	if not roll then
		return
	end

	E.State.RollBusy = true

	task.spawn(function()
		E.touch(roll.Part, roll)
		task.wait(0.06)
		E.State.RollBusy = false
	end)
end

--====================================================
-- SMART-SCORING
--====================================================

E.Weight = {
	RPUpgrade = {
		RPM = 1.34,
		LuckMulti = 1.31,
		Lesschancelower = 1.22,
		UpgradesCashMulti = 1.14,
	},

	SMUpgrade = {
		LuckMulti = 1.34,
		MultiOriginal = 1.24,
		RPM = 1.22,
	},

	PPUpgrade = {
		PrTime = 1.32,
		PRPM = 1.29,
		PPM = 1.23,
		PSM = 1.20,
	},

	CloverUpgrade = {
		CLM = 1.34,
		CRPM = 1.30,
		CM = 1.25,
		CLSM = 1.17,
		CLPP = 1.13,
	},
}

E.CurrencyForCategory = {
	RPUpgrade = "RebirthPoints",
	SMUpgrade = "UpgradesMultiMulti",
	PPUpgrade = "PrestigePoints",
	CloverUpgrade = "Clovers",
}

function E.categoryEnabled(category)
	if category == "RPUpgrade" then
		return E.Config.AutoRPUpgrades
	elseif category == "SMUpgrade" then
		return E.Config.AutoSMUpgrades
	elseif category == "PPUpgrade" then
		return E.Config.AutoPPUpgrades
	elseif category == "CloverUpgrade" then
		return E.Config.AutoCloverUpgrades
	end

	return false
end

function E.baseWeight(meta)
	local category = E.Weight[meta.Category]
	local weight =
		category
		and category[meta.GiveStat]
		or 0.82

	if E.Config.Strategy == "FastestRarity" then
		if meta.GiveStat == "LuckMulti"
			or meta.GiveStat
				== "Lesschancelower"
			or meta.GiveStat == "CLM"
			or meta.GiveStat == "CM"
		then
			weight += 0.27
		else
			weight -= 0.03
		end
	elseif E.Config.Strategy == "ResetRush" then
		if meta.GiveStat == "RPM"
			or meta.GiveStat == "PRPM"
			or meta.GiveStat == "PSM"
			or meta.GiveStat == "CRPM"
			or meta.GiveStat
				== "UpgradesCashMulti"
		then
			weight += 0.28
		else
			weight -= 0.02
		end
	end

	return weight
end

function E.operationFactor(meta)
	if meta.BaseGiveRaw
		and string.sub(
			meta.BaseGiveRaw,
			1,
			1
		) == "*"
	then
		return math.max(
			1,
			tonumber(
				string.sub(
					meta.BaseGiveRaw,
					2
				)
			) or 1
		)
	end

	local gain = E.gain(meta)
	local current =
		E.getStat(meta.GiveStat or "")

	if gain.Log10 == N.NEG_INF then
		return 1.05
	end

	if current.Log10 == N.NEG_INF then
		return 2
	end

	local relativeLog =
		N.ratioLog(gain, current)

	if relativeLog > 6 then
		return 10
	end

	local relative =
		10 ^ math.max(-12, relativeLog)

	return math.max(1.01, 1 + relative)
end

function E.bankRetention(
	currencyName,
	cost,
	balance
)
	local elasticity =
		E.BankElasticity[currencyName]
		or 0

	if elasticity <= 0 then
		return 1
	end

	if not cost.Number or not balance.Number then
		local fraction =
			N.costFraction(cost, balance)

		return math.max(
			0.04,
			(1 - fraction) ^ elasticity
		)
	end

	if balance.Number <= 0 then
		return 1
	end

	local remaining =
		math.max(
			0,
			(balance.Number - cost.Number)
				/ balance.Number
		)

	return math.max(
		0.04,
		remaining ^ elasticity
	)
end

function E.upgradeCandidate(meta)
	if E.isBought(meta)
		or not E.categoryEnabled(
			meta.Category
		)
	then
		return nil
	end

	local currencyName =
		E.CurrencyForCategory[
			meta.Category
		]

	if not currencyName then
		return nil
	end

	local cost = E.currentCost(meta)

	if not cost
		or cost.Log10 == N.NEG_INF
	then
		return nil
	end

	local balance =
		E.getStat(currencyName)

	if not N.ge(balance, cost) then
		return nil
	end

	local directFactor =
		E.operationFactor(meta)

	local retention =
		E.bankRetention(
			currencyName,
			cost,
			balance
		)

	-- Das ist der zentrale Punkt:
	-- Ein RP-Kauf wird nicht nur anhand des Upgrades bewertet.
	-- Der Verlust der angesparten RP-Bank wird mit einberechnet,
	-- weil diese selbst weitere Multiplikationen beeinflusst.
	local effectiveFactor =
		directFactor * retention

	local score =
		E.baseWeight(meta) * 100

	score += math.log10(
		math.max(1e-6, effectiveFactor)
	) * 125

	score += math.max(
		0,
		N.ratioLog(balance, cost)
	) * 20

	score -=
		N.costFraction(cost, balance) * 85

	if meta.Repeatable then
		score += 32
	else
		-- Einmalige Meilenstein-Upgrades dürfen
		-- nicht von den zwei RP-Repeatables verdrängt werden.
		score += 230
	end

	if meta.Category == "RPUpgrade"
		and meta.Repeatable
	then
		if meta.GiveStat == "RPM" then
			score += 18
		elseif meta.GiveStat
			== "LuckMulti"
		then
			score += 15
		end
	end

	local name =
		E.labelText(meta.Up1)

	if name == "" then
		name = meta.Name
	end

	return {
		Meta = meta,
		Name = name,
		Type = meta.Category,
		Score = score,
		Cost = cost,
		Details = string.format(
			"Kosten %s | direkt x%.3f | "
				.. "Bank x%.3f | %s",
			N.format(cost),
			directFactor,
			retention,
			meta.Repeatable
				and "wiederholbar"
				or "einmalig"
		),
	}
end

function E.cheapestUnbought(
	category,
	currencyName
)
	local result = nil

	for _, meta in ipairs(
		E.category(category)
	) do
		if not E.isBought(meta) then
			local cost =
				E.currentCost(meta)

			if cost
				and cost.Log10
					~= N.NEG_INF
				and (
					not result
					or cost.Log10
						< result.Cost.Log10
				)
			then
				result = {
					Meta = meta,
					Cost = cost,
					Currency =
						E.getStat(
							currencyName
						),
				}
			end
		end
	end

	return result
end

function E.resetCandidate(
	meta,
	name,
	currencyName,
	upgradeCategory,
	baseScore,
	minimumFraction
)
	if not meta or not meta.Part.Parent then
		return nil
	end

	local gain = E.gain(meta)

	if gain.Log10 == N.NEG_INF then
		return nil
	end

	local current =
		E.getStat(currencyName)

	local after = N.add(current, gain)

	local nextUpgrade =
		E.cheapestUnbought(
			upgradeCategory,
			currencyName
		)

	local reachesUpgrade =
		nextUpgrade
		and N.ge(
			after,
			nextUpgrade.Cost
		)
		or false

	local fractionLog =
		N.ratioLog(gain, current)

	if not reachesUpgrade
		and current.Log10 ~= N.NEG_INF
		and fractionLog
			< math.log10(minimumFraction)
	then
		return nil
	end

	local score = baseScore

	if reachesUpgrade then
		score += 190
	end

	if fractionLog == math.huge then
		score += 160
	else
		score += math.max(
			-1,
			fractionLog
		) * 55
	end

	if E.Config.Strategy == "ResetRush" then
		score += 75
	elseif E.Config.Strategy
		== "FastestRarity"
	then
		score -= 20
	end

	return {
		Meta = meta,
		Name = name,
		Type = "Reset",
		Score = score,
		Gain = gain,
		Details = string.format(
			"Gewinn %s%s",
			N.format(gain),
			reachesUpgrade
				and " | erreicht nächstes Upgrade"
				or ""
		),
	}
end

function E.collectResetCandidates(list)
	local rarity = E.small("Rarity")

	if E.Config.AutoAscension
		and rarity >= 250
	then
		local meta = E.single("Ascension")

		if meta then
			table.insert(list, {
				Meta = meta,
				Name = "Ascension",
				Type = "Reset",
				Score = 1120,
				Gain = E.gain(meta),
				Details =
					"Höchste verfügbare Reset-Ebene",
			})
		end
	end

	if E.Config.AutoPrestige
		and rarity >= 99
	then
		local candidate =
			E.resetCandidate(
				E.single("Prestige"),
				"Prestige",
				"PrestigePoints",
				"PPUpgrade",
				820,
				0.60
			)

		if candidate then
			table.insert(list, candidate)
		end
	end

	if E.Config.AutoSuperMultiplier then
		local meta = E.single("SuperReset")

		if meta then
			local required =
				E.currentCost(meta)

			if required
				and N.ge(
					E.getStat(
						"MultiOriginal"
					),
					required
				)
			then
				local candidate =
					E.resetCandidate(
						meta,
						"Super Multiplier",
						"UpgradesMultiMulti",
						"SMUpgrade",
						650,
						0.45
					)

				if candidate then
					table.insert(
						list,
						candidate
					)
				end
			end
		end
	end

	if E.Config.AutoRebirth
		and rarity >= 5
	then
		local candidate =
			E.resetCandidate(
				E.single("Rebirth"),
				"Rebirth",
				"RebirthPoints",
				"RPUpgrade",
				430,
				0.30
			)

		if candidate then
			table.insert(list, candidate)
		end
	end
end

function E.nextRarityMilestone()
	local best = E.small("BestRarity")
	local nextValue = math.huge

	for _, meta in ipairs(
		E.category("RarityMultiplier")
	) do
		local required =
			E.requiredRarity(meta)

		if required > best
			and required < nextValue
		then
			nextValue = required
		end
	end

	if best < 99 then
		nextValue = math.min(nextValue, 99)
	end

	if best < 250 then
		nextValue = math.min(nextValue, 250)
	end

	if nextValue == math.huge then
		return nil
	end

	return nextValue
end

function E.raritySpendCandidate(reserve)
	if not E.Config.AutoRaritySpend then
		return nil
	end

	local current = E.small("Rarity")
	local best = E.small("BestRarity")
	local available = current - reserve

	if available <= 0 then
		return nil
	end

	local selected = nil

	for _, meta in ipairs(
		E.category("RarityMultiplier")
	) do
		local costObject =
			E.currentCost(meta)

		local cost =
			costObject and costObject.Number

		local required =
			E.requiredRarity(meta)

		if cost
			and cost > 0
			and cost <= available
			and best >= required
		then
			local gain = E.gain(meta)

			if gain.Log10 ~= N.NEG_INF then
				local efficiency =
					gain.Log10
					- math.log10(cost)

				-- Rarity-Spend bleibt absichtlich
				-- unter Reset- und guten Shop-Aktionen.
				local score =
					150 + efficiency * 55

				local name =
					E.labelText(meta.Up3)

				if name == "" then
					name = meta.Name
				end

				local candidate = {
					Meta = meta,
					Name = name,
					Type = "RaritySpend",
					Score = score,
					Details = string.format(
						"Rarity-Kosten %.3f | "
							.. "Multiplier %s",
						cost,
						N.format(gain)
					),
				}

				if not selected
					or candidate.Score
						> selected.Score
				then
					selected = candidate
				end
			end
		end
	end

	return selected
end

function E.normalRarityReserve()
	local current = E.small("Rarity")
	local best = E.small("BestRarity")
	local milestone =
		E.nextRarityMilestone()

	if milestone then
		local distance = milestone - best

		if distance > 0
			and distance
				<= math.max(
					1,
					best
						* E.Limits
							.NearMilestoneFraction
				)
		then
			return current
		end
	end

	return math.max(
		5,
		best
			* E.Limits.RarityReserveFraction
	)
end

function E.collectUpgradeCandidates(list)
	for category in pairs(
		E.CurrencyForCategory
	) do
		if E.categoryEnabled(category) then
			for _, meta in ipairs(
				E.category(category)
			) do
				local candidate =
					E.upgradeCandidate(meta)

				if candidate then
					table.insert(
						list,
						candidate
					)
				end
			end
		end
	end
end

function E.runeCandidate(
	enabled,
	category,
	name,
	currencyName,
	cost
)
	if not enabled then
		return nil
	end

	if not N.ge(
		E.getStat(currencyName),
		cost
	) then
		return nil
	end

	local meta = E.single(category)

	if not meta then
		return nil
	end

	return {
		Meta = meta,
		Name = name,
		Type = "Rune",
		Score = 35,
		Details =
			"Rune erst nach sicheren Upgrades",
	}
end

function E.collectRuneCandidates(list)
	local candidates = {
		E.runeCandidate(
			E.Config.AutoPrestigeRunes,
			"PrestigeRune",
			"Prestige-Rune",
			"PrestigePoints",
			N.fromNumber(10)
		),

		E.runeCandidate(
			E.Config.AutoCloverRunes,
			"CloverRune",
			"Clover-Rune",
			"Clovers",
			N.fromNumber(250)
		),

		E.runeCandidate(
			E.Config.AutoMultiplierRunes,
			"MultiplierRune",
			"Multiplier-Rune",
			"MultiOriginal",
			N.parseDisplay("1Ce")
		),

		E.runeCandidate(
			E.Config.AutoPlantRunes,
			"PlantRune",
			"Plant-Rune",
			"Clovers",
			N.parseDisplay("100K")
		),
	}

	for _, candidate in ipairs(candidates) do
		if candidate then
			table.insert(list, candidate)
		end
	end
end

function E.sortCandidates(list)
	table.sort(list, function(a, b)
		if a.Score == b.Score then
			return a.Name < b.Name
		end

		return a.Score > b.Score
	end)
end

function E.buildCandidates()
	local candidates = {}

	E.collectResetCandidates(candidates)
	E.collectUpgradeCandidates(candidates)

	if #candidates == 0
		and E.Config.NormalRaritySpend
	then
		local spend =
			E.raritySpendCandidate(
				E.normalRarityReserve()
			)

		if spend then
			table.insert(candidates, spend)
		end
	end

	if #candidates == 0 then
		E.collectRuneCandidates(candidates)
	end

	E.sortCandidates(candidates)
	E.State.TopDecisions = candidates

	return candidates
end

function E.executeReset(candidate)
	local reserve = 0

	if candidate.Name == "Rebirth" then
		reserve = 5
	elseif candidate.Name == "Prestige" then
		reserve = 99
	elseif candidate.Name == "Ascension" then
		reserve = 250
	end

	if E.Config.SpendBeforeReset
		and E.Config.AutoRaritySpend
	then
		if E.State.PreResetName
			~= candidate.Name
		then
			E.State.PreResetName =
				candidate.Name

			E.State.PreResetSpends = 0
		end

		if E.State.PreResetSpends
			< E.Limits.MaxPreResetSpends
		then
			local spend =
				E.raritySpendCandidate(
					reserve
				)

			if spend then
				E.State.PreResetSpends += 1

				E.State.Plan =
					"Vor "
					.. candidate.Name
					.. ": "
					.. spend.Name

				E.State.DecisionType =
					"Rarity vor Reset"

				E.State.DecisionScore =
					spend.Score

				return E.queue(
					spend.Meta,
					spend.Name
				)
			end
		end
	end

	E.State.PreResetName = nil
	E.State.PreResetSpends = 0

	E.State.Plan =
		candidate.Name
		.. " durchführen | "
		.. candidate.Details

	E.State.DecisionType = candidate.Type
	E.State.DecisionScore = candidate.Score

	return E.queue(
		candidate.Meta,
		candidate.Name
	)
end

function E.plannerStep()
	if not E.Config.Master
		or E.State.Busy
	then
		return
	end

	local candidates = E.buildCandidates()
	local best = candidates[1]

	if not best then
		E.State.Plan =
			"Rarity erhöhen und auf "
			.. "den nächsten sinnvollen Kauf sparen"

		E.State.DecisionType = "Warten"
		E.State.DecisionScore = 0
		E.notify()

		return
	end

	if best.Type == "Reset" then
		E.executeReset(best)
		return
	end

	E.State.PreResetName = nil
	E.State.PreResetSpends = 0

	E.State.Plan =
		best.Name
		.. " | "
		.. best.Details

	E.State.DecisionType = best.Type
	E.State.DecisionScore = best.Score

	E.queue(best.Meta, best.Name)
end

--====================================================
-- KOMMANDOS VOM GUI
--====================================================

function E.setOption(key, value)
	if E.Config[key] == nil then
		return
	end

	E.Config[key] = value == true

	if key == "ButtonNoclip" then
		E.applyGhostSetting()
	elseif key == "StageButtons"
		and not E.Config.StageButtons
	then
		E.restoreStagedButtons()
	end

	E.markDirty()
	E.notify(true)
end

function E.cycleStrategy()
	local strategies = {
		"Balanced",
		"FastestRarity",
		"ResetRush",
	}

	local current = 1

	for index, strategy in ipairs(
		strategies
	) do
		if strategy == E.Config.Strategy then
			current = index
			break
		end
	end

	current += 1

	if current > #strategies then
		current = 1
	end

	E.Config.Strategy =
		strategies[current]

	E.State.LastAction =
		"Strategie: "
		.. E.Config.Strategy

	E.markDirty()
	E.notify(true)
end

function E.printDebug()
	print("")
	print("========================================")
	print("SMART RNG V5 ENGINE DEBUG")
	print("========================================")
	print("Ready:", E.State.Ready)
	print("Master:", E.Config.Master)
	print("Strategie:", E.Config.Strategy)
	print("Touch:", E.State.TouchMode)
	print("Touch bestätigt:", E.State.TouchConfirmed)
	print("Geparkter/Aktiver Button:", E.State.StagedButtonName)
	print("Fehler:", E.State.Error or "keiner")
	print("Plan:", E.State.Plan)
	print("RP:", N.format(E.getStat("RebirthPoints")))
	print("RPM:", N.format(E.getStat("RPM")))
	print("Luck:", N.format(E.getStat("LuckMulti")))
	print("SM:", N.format(E.getStat("UpgradesMultiMulti")))
	print("PP:", N.format(E.getStat("PrestigePoints")))
	print("")
	print("Top-Entscheidungen:")

	for index, candidate in ipairs(
		E.State.TopDecisions
	) do
		if index > 8 then
			break
		end

		print(string.format(
			"%d. %s | %s | Score %.2f | %s",
			index,
			candidate.Name,
			candidate.Type,
			candidate.Score,
			candidate.Details
		))
	end

	print("========================================")
end

CommandEvent.Event:Connect(function(
	command,
	argument,
	value
)
	if command == "ToggleMaster" then
		E.Config.Master =
			not E.Config.Master

		E.State.LastAction =
			E.Config.Master
			and "Smart-Automatik gestartet"
			or "Smart-Automatik gestoppt"

		if not E.Config.Master then
			E.parkActiveButton(nil)
		end

		E.markDirty()
		E.notify(true)

	elseif command == "SetMaster" then
		E.Config.Master = argument == true

		if not E.Config.Master then
			E.parkActiveButton(nil)
		end

		E.markDirty()
		E.notify(true)

	elseif command == "ToggleOption" then
		local key = tostring(argument)

		if E.Config[key] ~= nil then
			E.setOption(
				key,
				not E.Config[key]
			)
		end

	elseif command == "SetOption" then
		E.setOption(
			tostring(argument),
			value
		)

	elseif command == "CycleStrategy" then
		E.cycleStrategy()

	elseif command == "Rescan" then
		E.scanButtons()
		E.scanGhosts()

		E.State.LastAction =
			"Buttons neu eingelesen"

		E.markDirty()
		E.notify(true)

	elseif command == "PrintDebug" then
		E.printDebug()
	end
end)

--====================================================
-- INITIALISIERUNG
--====================================================

local function initialize()
	E.watchStats()
	E.scanButtons()
	E.scanGhosts()
	E.applyGhostSetting()

	workspace.DescendantAdded:Connect(
		function(instance)
			if instance:IsA("BasePart")
				and E.insideGhostRoot(instance)
			then
				task.defer(function()
					E.registerGhostPart(instance)
				end)
			end

			if E.isTouchPart(instance) then
				task.defer(function()
					E.registerButton(instance)
				end)
			end
		end
	)

	E.State.Ready = true
	E.State.LastAction =
		"Engine bereit"

	E.State.Plan =
		"F10 oder GUI verwenden"

	EngineReadyValue.Value = true
	EngineErrorValue.Value = ""

	E.notify(true)
end

local success, result =
	xpcall(initialize, debug.traceback)

if not success then
	E.State.Error = tostring(result)
	E.State.LastAction =
		"Initialisierungsfehler"

	E.State.Plan =
		"GUI öffnen und F6-Debug ausführen"

	EngineErrorValue.Value =
		E.State.Error

	warn(
		"[SMART RNG V5.1 ENGINE FEHLER]",
		result
	)
end

--====================================================
-- LEICHTE LOOPS
--====================================================

task.spawn(function()
	while task.wait(E.Timing.RollInterval) do
		if E.Config.Master
			and E.Config.AutoRoll
			and not E.State.Busy
		then
			E.rollTouch()
		end
	end
end)

task.spawn(function()
	while task.wait(E.Timing.ThinkInterval) do
		if E.Config.Master
			and not E.State.Busy
		then
			local ok, err =
				pcall(function()
					E.plannerStep()
				end)

			if not ok then
				E.State.Error = tostring(err)
				E.State.LastAction =
					"Planerfehler"

				EngineErrorValue.Value =
					E.State.Error

				E.notify(true)
			end
		end
	end
end)

print("[SMART RNG V5.1 ENGINE] Geladen.")
print("[SMART RNG V5.1 ENGINE] Warte auf GUI-LocalScript.") 

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Next Script
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

--[[
	SMART RNG V5.1 - GUI
	Dies ist LocalScript 2 von 2.

	Empfohlener Ort:
	StarterPlayer > StarterPlayerScripts

	Das Engine-Script und dieses GUI-Script müssen beide laufen.
	Die Startreihenfolge ist egal. Das GUI wartet automatisch auf die Engine.

	F10        = Master-Automatik an/aus
	RightShift = GUI ein-/ausblenden
	F6         = Engine-Debug in die Konsole schreiben
]]

if not game:IsLoaded() then
	game.Loaded:Wait()
end

local Players = game:GetService("Players")
local UserInputService =
	game:GetService("UserInputService")

local LP = Players.LocalPlayer
local PlayerGui =
	LP:WaitForChild("PlayerGui")

local PlayerScripts =
	LP:WaitForChild("PlayerScripts")

--====================================================
-- GUI-STATE
--====================================================

local G = {
	Bridge = nil,
	Command = nil,
	StateChanged = nil,
	GetSnapshot = nil,
	ReadyValue = nil,
	ErrorValue = nil,

	Snapshot = nil,
	Connected = false,

	Gui = nil,
	Main = nil,
	MasterButton = nil,
	StrategyButton = nil,
	PlanLabel = nil,
	StatusLabel = nil,
	StatsLabel = nil,
	DecisionLabel = nil,
	EngineLabel = nil,

	ToggleButtons = {},
}

local COLORS = {
	Background = Color3.fromRGB(21, 22, 28),
	Panel = Color3.fromRGB(31, 33, 41),
	Panel2 = Color3.fromRGB(43, 46, 56),

	On = Color3.fromRGB(43, 139, 82),
	Off = Color3.fromRGB(132, 55, 61),
	Accent = Color3.fromRGB(65, 108, 205),

	Text = Color3.fromRGB(242, 242, 245),
	Muted = Color3.fromRGB(166, 170, 182),
	Plan = Color3.fromRGB(180, 208, 255),

	Error = Color3.fromRGB(255, 120, 120),
	Ready = Color3.fromRGB(115, 255, 155),
}

local TOGGLES = {
	{"AutoRoll", "Auto Roll"},
	{"AutoRaritySpend", "Rarity → Multiplier"},
	{"AutoRebirth", "Auto Rebirth"},
	{"AutoRPUpgrades", "RP-Upgrades"},
	{"AutoSuperMultiplier", "Super Multiplier"},
	{"AutoSMUpgrades", "SM-Upgrades"},
	{"AutoPrestige", "Auto Prestige"},
	{"AutoPPUpgrades", "PP-Upgrades"},
	{"AutoAscension", "Auto Ascension"},
	{"AutoCloverUpgrades", "Clover-Upgrades"},

	{"AutoCloverRunes", "Clover-Runes"},
	{"AutoPrestigeRunes", "Prestige-Runes"},
	{"AutoMultiplierRunes", "Multiplier-Runes"},
	{"AutoPlantRunes", "Plant-Runes"},

	{"ButtonNoclip", "Button-Noclip"},
	{"AntiKnockback", "Anti-Knockback"},
	{"StageButtons", "Button-Teleport"},
	{"ReliableTouch", "Touch-Puls"},
}

--====================================================
-- UI-HILFSFUNKTIONEN
--====================================================

local function addCorner(instance, radius)
	local corner = Instance.new("UICorner")
	corner.CornerRadius =
		UDim.new(0, radius or 8)
	corner.Parent = instance
end

local function makeLabel(
	parent,
	text,
	position,
	size,
	textSize
)
	local label = Instance.new("TextLabel")
	label.BackgroundTransparency = 1
	label.Position = position
	label.Size = size
	label.Font = Enum.Font.Gotham
	label.Text = text
	label.TextColor3 = COLORS.Text
	label.TextSize = textSize or 13
	label.TextWrapped = true
	label.TextXAlignment =
		Enum.TextXAlignment.Left
	label.TextYAlignment =
		Enum.TextYAlignment.Center
	label.Parent = parent

	return label
end

local function makeButton(
	parent,
	text,
	position,
	size
)
	local button = Instance.new("TextButton")
	button.Position = position
	button.Size = size
	button.BackgroundColor3 = COLORS.Panel2
	button.BorderSizePixel = 0
	button.AutoButtonColor = true
	button.Font = Enum.Font.GothamBold
	button.Text = text
	button.TextColor3 = COLORS.Text
	button.TextSize = 12
	button.Parent = parent

	addCorner(button, 7)

	return button
end

local function makeDraggable(frame, handle)
	local dragging = false
	local dragStart = nil
	local startPosition = nil

	handle.InputBegan:Connect(function(input)
		if input.UserInputType
			== Enum.UserInputType.MouseButton1
			or input.UserInputType
			== Enum.UserInputType.Touch
		then
			dragging = true
			dragStart = input.Position
			startPosition = frame.Position
		end
	end)

	UserInputService.InputChanged:Connect(
		function(input)
			if not dragging then
				return
			end

			if input.UserInputType
				== Enum.UserInputType.MouseMovement
				or input.UserInputType
				== Enum.UserInputType.Touch
			then
				local delta =
					input.Position - dragStart

				frame.Position = UDim2.new(
					startPosition.X.Scale,
					startPosition.X.Offset
						+ delta.X,
					startPosition.Y.Scale,
					startPosition.Y.Offset
						+ delta.Y
				)
			end
		end
	)

	UserInputService.InputEnded:Connect(
		function(input)
			if input.UserInputType
				== Enum.UserInputType.MouseButton1
				or input.UserInputType
				== Enum.UserInputType.Touch
			then
				dragging = false
			end
		end
	)
end

--====================================================
-- GUI ERSTELLEN
--====================================================

local function createGui()
	local old =
		PlayerGui:FindFirstChild(
			"_SmartRNGV5Gui"
		)

	if old then
		old:Destroy()
	end

	local screen = Instance.new("ScreenGui")
	screen.Name = "_SmartRNGV5Gui"
	screen.ResetOnSpawn = false
	screen.DisplayOrder = 1700
	screen.Parent = PlayerGui
	G.Gui = screen

	local main = Instance.new("Frame")
	main.Size = UDim2.fromOffset(570, 760)
	main.Position =
		UDim2.new(0, 18, 0.5, -380)
	main.BackgroundColor3 =
		COLORS.Background
	main.BorderSizePixel = 0
	main.Active = true
	main.Parent = screen
	G.Main = main

	addCorner(main, 11)

	local titleBar = Instance.new("Frame")
	titleBar.Size =
		UDim2.new(1, 0, 0, 44)
	titleBar.BackgroundColor3 =
		COLORS.Panel
	titleBar.BorderSizePixel = 0
	titleBar.Active = true
	titleBar.Parent = main

	addCorner(titleBar, 11)

	local title = makeLabel(
		titleBar,
		"Smart RNG V5.1",
		UDim2.fromOffset(14, 0),
		UDim2.new(1, -110, 1, 0),
		18
	)

	title.Font = Enum.Font.GothamBold

	local hide = makeButton(
		titleBar,
		"—",
		UDim2.new(1, -42, 0, 8),
		UDim2.fromOffset(32, 28)
	)

	hide.MouseButton1Click:Connect(function()
		screen.Enabled = false
	end)

	makeDraggable(main, titleBar)

	G.EngineLabel = makeLabel(
		main,
		"Warte auf Engine-LocalScript …",
		UDim2.fromOffset(14, 48),
		UDim2.new(1, -28, 0, 28),
		12
	)

	G.EngineLabel.TextColor3 = COLORS.Muted

	G.MasterButton = makeButton(
		main,
		"SMART AUTO: AUS",
		UDim2.fromOffset(12, 80),
		UDim2.new(0.58, -18, 0, 42)
	)

	G.MasterButton.BackgroundColor3 =
		COLORS.Off

	G.StrategyButton = makeButton(
		main,
		"Strategie: Balanced",
		UDim2.new(0.58, 0, 0, 80),
		UDim2.new(0.42, -12, 0, 42)
	)

	G.StrategyButton.BackgroundColor3 =
		COLORS.Accent

	G.PlanLabel = makeLabel(
		main,
		"Plan: Warte auf Engine",
		UDim2.fromOffset(14, 128),
		UDim2.new(1, -28, 0, 58),
		12
	)

	G.PlanLabel.TextColor3 = COLORS.Plan

	G.StatusLabel = makeLabel(
		main,
		"Aktion: keine",
		UDim2.fromOffset(14, 188),
		UDim2.new(1, -28, 0, 70),
		11
	)

	G.StatusLabel.TextColor3 =
		COLORS.Muted

	local statsPanel = Instance.new("Frame")
	statsPanel.Position =
		UDim2.fromOffset(12, 264)
	statsPanel.Size =
		UDim2.new(1, -24, 0, 126)
	statsPanel.BackgroundColor3 =
		COLORS.Panel
	statsPanel.BorderSizePixel = 0
	statsPanel.Parent = main

	addCorner(statsPanel, 8)

	G.StatsLabel = makeLabel(
		statsPanel,
		"Werte werden geladen …",
		UDim2.fromOffset(12, 8),
		UDim2.new(1, -24, 1, -16),
		12
	)

	G.StatsLabel.Font = Enum.Font.Code

	local decisionPanel = Instance.new("Frame")
	decisionPanel.Position =
		UDim2.fromOffset(12, 398)
	decisionPanel.Size =
		UDim2.new(1, -24, 0, 104)
	decisionPanel.BackgroundColor3 =
		COLORS.Panel
	decisionPanel.BorderSizePixel = 0
	decisionPanel.Parent = main

	addCorner(decisionPanel, 8)

	G.DecisionLabel = makeLabel(
		decisionPanel,
		"Top-Entscheidungen werden geladen …",
		UDim2.fromOffset(12, 6),
		UDim2.new(1, -24, 1, -12),
		11
	)

	G.DecisionLabel.Font = Enum.Font.Code

	local toolBar = Instance.new("Frame")
	toolBar.Position =
		UDim2.fromOffset(12, 510)
	toolBar.Size =
		UDim2.new(1, -24, 0, 34)
	toolBar.BackgroundTransparency = 1
	toolBar.Parent = main

	local toolLayout = Instance.new("UIListLayout")
	toolLayout.FillDirection =
		Enum.FillDirection.Horizontal
	toolLayout.Padding = UDim.new(0, 7)
	toolLayout.Parent = toolBar

	local rescanButton = makeButton(
		toolBar,
		"Buttons neu scannen",
		UDim2.new(),
		UDim2.new(0.5, -4, 1, 0)
	)

	local debugButton = makeButton(
		toolBar,
		"F6-Debug ausgeben",
		UDim2.new(),
		UDim2.new(0.5, -4, 1, 0)
	)

	local scroll =
		Instance.new("ScrollingFrame")

	scroll.Position =
		UDim2.fromOffset(12, 552)

	scroll.Size =
		UDim2.new(1, -24, 1, -564)

	scroll.BackgroundColor3 =
		COLORS.Panel

	scroll.BorderSizePixel = 0
	scroll.ScrollBarThickness = 7
	scroll.CanvasSize =
		UDim2.fromOffset(0, 0)
	scroll.Parent = main

	addCorner(scroll, 8)

	local padding = Instance.new("UIPadding")
	padding.PaddingTop = UDim.new(0, 8)
	padding.PaddingBottom = UDim.new(0, 8)
	padding.PaddingLeft = UDim.new(0, 8)
	padding.PaddingRight = UDim.new(0, 8)
	padding.Parent = scroll

	local layout = Instance.new("UIGridLayout")
	layout.CellSize =
		UDim2.new(0.5, -6, 0, 38)

	layout.CellPadding =
		UDim2.fromOffset(8, 8)

	layout.SortOrder =
		Enum.SortOrder.LayoutOrder

	layout.Parent = scroll

	for index, entry in ipairs(TOGGLES) do
		local key = entry[1]
		local displayName = entry[2]

		local button = makeButton(
			scroll,
			displayName,
			UDim2.new(),
			UDim2.new()
		)

		button.LayoutOrder = index

		button:SetAttribute(
			"OptionKey",
			key
		)

		button:SetAttribute(
			"DisplayName",
			displayName
		)

		G.ToggleButtons[key] = button

		button.MouseButton1Click:Connect(
			function()
				if G.Command then
					G.Command:Fire(
						"ToggleOption",
						key
					)
				end
			end
		)
	end

	layout:GetPropertyChangedSignal(
		"AbsoluteContentSize"
	):Connect(function()
		scroll.CanvasSize =
			UDim2.fromOffset(
				0,
				layout.AbsoluteContentSize.Y
					+ 16
			)
	end)

	G.MasterButton.MouseButton1Click:Connect(
		function()
			if G.Command then
				G.Command:Fire(
					"ToggleMaster"
				)
			end
		end
	)

	G.StrategyButton.MouseButton1Click:Connect(
		function()
			if G.Command then
				G.Command:Fire(
					"CycleStrategy"
				)
			end
		end
	)

	rescanButton.MouseButton1Click:Connect(
		function()
			if G.Command then
				G.Command:Fire("Rescan")
			end
		end
	)

	debugButton.MouseButton1Click:Connect(
		function()
			if G.Command then
				G.Command:Fire(
					"PrintDebug"
				)
			end
		end
	)
end

--====================================================
-- BRIDGE VERBINDEN
--====================================================

local function connectBridge()
	while not G.Connected do
		local bridge =
			PlayerScripts:FindFirstChild(
				"_SmartRNGBridge"
			)

		if bridge then
			local command =
				bridge:FindFirstChild(
					"Command"
				)

			local stateChanged =
				bridge:FindFirstChild(
					"StateChanged"
				)

			local getSnapshot =
				bridge:FindFirstChild(
					"GetSnapshot"
				)

			local ready =
				bridge:FindFirstChild(
					"EngineReady"
				)

			local errorValue =
				bridge:FindFirstChild(
					"EngineError"
				)

			if command
				and stateChanged
				and getSnapshot
				and ready
				and errorValue
			then
				G.Bridge = bridge
				G.Command = command
				G.StateChanged = stateChanged
				G.GetSnapshot = getSnapshot
				G.ReadyValue = ready
				G.ErrorValue = errorValue
				G.Connected = true

				stateChanged.Event:Connect(
					function()
						-- Der normale Refresh-Loop
						-- liest anschließend den Snapshot.
					end
				)

				return
			end
		end

		task.wait(0.25)
	end
end

local function requestSnapshot()
	if not G.GetSnapshot then
		return nil
	end

	local success, snapshot = pcall(function()
		return G.GetSnapshot:Invoke()
	end)

	if not success then
		return nil
	end

	return snapshot
end

--====================================================
-- GUI AKTUALISIEREN
--====================================================

local function updateToggleButtons(snapshot)
	local options = snapshot.Options or {}

	for key, button in pairs(
		G.ToggleButtons
	) do
		local enabled =
			options[key] == true

		local displayName =
			button:GetAttribute(
				"DisplayName"
			)

		button.Text =
			displayName
			.. (enabled and ": AN" or ": AUS")

		button.BackgroundColor3 =
			enabled
			and COLORS.On
			or COLORS.Off
	end
end

local function buildDecisionText(snapshot)
	local decisions = snapshot.Decisions or {}

	if #decisions == 0 then
		return "Keine kaufbare Aktion. "
			.. "Das Script spart oder rollt weiter."
	end

	local lines = {}

	for index, decision in ipairs(decisions) do
		if index > 3 then
			break
		end

		table.insert(
			lines,
			string.format(
				"%d. %s | %.1f\n   %s",
				index,
				tostring(decision.Name),
				tonumber(decision.Score) or 0,
				tostring(
					decision.Details or ""
				)
			)
		)
	end

	return table.concat(lines, "\n")
end

local function updateGui()
	if not G.Gui or not G.Gui.Enabled then
		return
	end

	if not G.Connected then
		G.EngineLabel.Text =
			"Warte auf Engine-LocalScript …"

		G.EngineLabel.TextColor3 =
			COLORS.Muted

		return
	end

	local snapshot = requestSnapshot()

	if not snapshot then
		G.EngineLabel.Text =
			"Engine verbunden, Snapshot fehlt"

		G.EngineLabel.TextColor3 =
			COLORS.Error

		return
	end

	G.Snapshot = snapshot

	if snapshot.Error
		and tostring(snapshot.Error) ~= ""
	then
		G.EngineLabel.Text =
			"Engine-Fehler: "
			.. tostring(snapshot.Error)

		G.EngineLabel.TextColor3 =
			COLORS.Error
	elseif snapshot.Ready then
		G.EngineLabel.Text =
			"Engine bereit | "
			.. tostring(snapshot.TouchMode)

		G.EngineLabel.TextColor3 =
			COLORS.Ready
	else
		G.EngineLabel.Text =
			"Engine initialisiert …"

		G.EngineLabel.TextColor3 =
			COLORS.Muted
	end

	G.MasterButton.Text =
		snapshot.Master
		and "SMART AUTO: AN"
		or "SMART AUTO: AUS"

	G.MasterButton.BackgroundColor3 =
		snapshot.Master
		and COLORS.On
		or COLORS.Off

	G.StrategyButton.Text =
		"Strategie: "
		.. tostring(snapshot.Strategy)

	G.PlanLabel.Text =
		"Plan: "
		.. tostring(snapshot.Plan)

	G.StatusLabel.Text = string.format(
		"Aktion: %s\n"
			.. "Entscheidung: %s | Score %.1f\n"
			.. "Buttons %d | Repeatables %d | Ghost %d | Aktiv %s",
		tostring(snapshot.LastAction),
		tostring(snapshot.DecisionType),
		tonumber(snapshot.DecisionScore)
			or 0,
		tonumber(snapshot.ButtonCount)
			or 0,
		tonumber(snapshot.RepeatableCount)
			or 0,
		tonumber(snapshot.GhostCount)
			or 0,
		tostring(snapshot.StagedButton or "Keiner")
	)

	local stats = snapshot.Stats or {}

	G.StatsLabel.Text = string.format(
		"Rarity: %s   Best: %s\n"
			.. "Luck: %s   Multiplier: %s\n"
			.. "RP: %s   RP-Multi: %s\n"
			.. "SM: %s   PP: %s\n"
			.. "Clovers: %s   Ascension: %s",
		tostring(stats.Rarity or "0"),
		tostring(stats.BestRarity or "0"),
		tostring(stats.Luck or "0"),
		tostring(stats.Multiplier or "0"),
		tostring(stats.RP or "0"),
		tostring(stats.RPM or "0"),
		tostring(stats.SM or "0"),
		tostring(stats.PP or "0"),
		tostring(stats.Clovers or "0"),
		tostring(stats.Ascension or "0")
	)

	G.DecisionLabel.Text =
		buildDecisionText(snapshot)

	updateToggleButtons(snapshot)
end

--====================================================
-- START
--====================================================

createGui()

task.spawn(function()
	connectBridge()
	updateGui()
end)

UserInputService.InputBegan:Connect(
	function(input, processed)
		if processed then
			return
		end

		if input.KeyCode == Enum.KeyCode.F10 then
			if G.Command then
				G.Command:Fire(
					"ToggleMaster"
				)
			end

		elseif input.KeyCode
			== Enum.KeyCode.RightShift
		then
			G.Gui.Enabled =
				not G.Gui.Enabled

			if G.Gui.Enabled then
				updateGui()
			end

		elseif input.KeyCode == Enum.KeyCode.F6 then
			if G.Command then
				G.Command:Fire(
					"PrintDebug"
				)
			end
		end
	end
)

task.spawn(function()
	while task.wait(0.30) do
		local success, result =
			pcall(updateGui)

		if not success then
			G.EngineLabel.Text =
				"GUI-Fehler: "
				.. tostring(result)

			G.EngineLabel.TextColor3 =
				COLORS.Error
		end
	end
end)

print("[SMART RNG V5.1 GUI] Geladen.")
print("[SMART RNG V5.1 GUI] Warte auf Engine.")

Features & Information

Two Scripts pls Execute both

Community Comments (0)

No comments yet for this script.

More Scripts for Cool Rarities! (UPDATE!)