--[[ 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.")