From ec334ba7b1a38f420a245add3efb017a24214040 Mon Sep 17 00:00:00 2001 From: joritochip Date: Wed, 23 Sep 2026 01:38:04 -0600 Subject: [PATCH 1/2] Modify donation panel --- MainModule/Client/UI/Default/UserPanel.luau | 220 +++++++++++++++----- MainModule/Server/Core/Variables.luau | 2 +- 2 files changed, 164 insertions(+), 58 deletions(-) diff --git a/MainModule/Client/UI/Default/UserPanel.luau b/MainModule/Client/UI/Default/UserPanel.luau index b68fc226c4..6f98289d55 100644 --- a/MainModule/Client/UI/Default/UserPanel.luau +++ b/MainModule/Client/UI/Default/UserPanel.luau @@ -63,7 +63,16 @@ return function(data, env) end }) - local function createPurchaseWindow(id, isAvatarAsset) + local function createWarningPrompt() + return UI.MakeGui("YesNoPrompt", { + Title = "Are you sure?", + Question = "You already have donor perks. Additional donations provide no extra benefits." + }) == "Yes" + end + + local function createPurchaseWindow(id, isAvatarAsset, showWarning) + if showWarning and not createWarningPrompt() then return end + local purchaseWindow = UI.Make("Window", { Name = "Purchase window"; Title = `Purchase asset`; @@ -80,10 +89,10 @@ return function(data, env) AnchorPoint = Vector2.new(0.5, 0); ZIndex = 2; TextScaled = true; - Text = "This game does not allow third party purchases. If you would like to purchase, please copy the link below and put it in your browser."; + Text = "If you would like to purchase, please copy the link below and put it in your browser."; }) - purchaseWindow:Add("TextBox", { + local box = purchaseWindow:Add("TextBox", { Size = UDim2.new(1, -20, 0, 30); Position = UDim2.new(0.5, 0, 1, -10); AnchorPoint = Vector2.new(0.5, 1); @@ -94,6 +103,24 @@ return function(data, env) Text = string.format("https://roblox.com/%s/%d/", if isAvatarAsset then "catalog" else "library", id) }) + local isFocused = false + + box.Focused:Connect(function() + isFocused = true + box.SelectionStart = 1 + box.CursorPosition = #box.Text + 1 + end) + + box.FocusLost:Connect(function() + isFocused = false + end) + + box:GetPropertyChangedSignal("CursorPosition"):Connect(function() + if not isFocused or box.CursorPosition == -1 then return end + box.SelectionStart = 1 + box.CursorPosition = #box.Text + 1 + end) + purchaseWindow:Ready() return purchaseWindow @@ -125,14 +152,8 @@ return function(data, env) end local function promptBulkPurchase(id: number, showWarning: boolean?, showSuccess: boolean?) - if showWarning then - if UI.MakeGui("YesNoPrompt", { - Title = "Are you sure?", - Question = "You already have donor perks. Additional donations provide no extra benefits." - }) ~= "Yes" then - return - end - end + if showWarning and not createWarningPrompt() then return end + local backupWindow, finishEvent finishEvent = service.RbxEvent(service.MarketplaceService.PromptBulkPurchaseFinished, function(p, status, items) if showSuccess and status == Enum.MarketplaceBulkPurchasePromptStatus.Completed then @@ -157,6 +178,17 @@ return function(data, env) end) end + local function setLocalPrice(button, id: number) + if not button then return end + + task.spawn(function() + local info = service.GetProductInfo(id, Enum.InfoType.Asset) + if info and type(info.PriceInRobux) == "number" then + button.Text = ` {info.PriceInRobux}` + end + end) + end + local function showTable(tab, setting) local tabPath = type(setting) == "table" and setting local setting = tabPath and tabPath[#tabPath-1] or setting @@ -564,7 +596,7 @@ return function(data, env) local dStatus = donorTab:Add("TextLabel", { Text = " Donor Status: "; - Size = UDim2.new(1, -10, 0, 20); + Size = UDim2.new(1, -145, 0, 20); Position = UDim2.new(0, 5, 0, 5); BackgroundTransparency = 0.25; TextXAlignment = "Left"; @@ -604,7 +636,7 @@ return function(data, env) donorTab:Add("TextLabel", { Text = " Donor Cape: "; - Size = UDim2.new(1, -10, 0, 20); + Size = UDim2.new(1, -145, 0, 20); Position = UDim2.new(0, 5, 0, 25); BackgroundTransparency = 0.8; TextXAlignment = "Left"; @@ -629,7 +661,7 @@ return function(data, env) donorTab:Add("TextLabel", { Text = " Cape Color: "; - Size = UDim2.new(1, -10, 0, 20); + Size = UDim2.new(1, -145, 0, 20); Position = UDim2.new(0, 5, 0, 45); BackgroundTransparency = 0.25; TextXAlignment = "Left"; @@ -662,7 +694,7 @@ return function(data, env) donorTab:Add("TextLabel", { Text = " Cape Material: "; - Size = UDim2.new(1, -10, 0, 20); + Size = UDim2.new(1, -145, 0, 20); Position = UDim2.new(0, 5, 0, 65); BackgroundTransparency = 0.8; TextXAlignment = "Left"; @@ -716,7 +748,7 @@ return function(data, env) donorTab:Add("TextLabel", { Text = " Cape Texture: "; - Size = UDim2.new(1, -10, 0, 20); + Size = UDim2.new(1, -145, 0, 20); Position = UDim2.new(0, 5, 0, 85); BackgroundTransparency = 0.25; TextXAlignment = "Left"; @@ -843,56 +875,126 @@ return function(data, env) TextXAlignment = "Left"; }) - local dFrame = donorTab:Add("Frame", { - Size = UDim2.new(0, 140, 1, -110); - Position = UDim2.new(1, -140, 0, 105); + local dFrame = donorTab:Add("ScrollingFrame", { + Size = UDim2.new(0, 140, 1, -10); + Position = UDim2.new(1, -140, 0, 5); BackgroundTransparency = 1; + AutomaticCanvasSize = Enum.AutomaticSize.Y; }) dFrame:Add("TextLabel", { Text = "Donate (Perks)"; TextXAlignment = "Center"; - Size = UDim2.new(1, 0, 0, 30); + Size = UDim2.new(1, -10, 0, 25); Position = UDim2.new(0, 5, 0, 0); BackgroundTransparency = 1; + Font = Enum.Font.SourceSansBold; }) - dFrame:Add("TextButton", { - Text = ` 300`; - Size = UDim2.new(1, -10, 0, 40); - Position = UDim2.new(0, 5, 0, 30); - BackgroundTransparency = 0.5; - BackgroundColor3 = Color3.fromRGB(231, 6, 141); - OnClick = function() - promptBulkPurchase(6878510605, playerData.isDonor, true) - end + dFrame:Add("TextLabel", { + Text = "All donations grant the same perks"; + TextXAlignment = "Center"; + Size = UDim2.new(1, -10, 0, 30); + TextSize = 14; + TextWrapped = true; + Position = UDim2.new(0, 5, 0, 20); + BackgroundTransparency = 1; + Font = Enum.Font.SourceSans; }) - - dFrame:Add("TextButton", { - Text = ` 500`; - Size = UDim2.new(0, 62, 0, 25); - Position = UDim2.new(0, 5, 0, 76); - BackgroundTransparency = 0.5; - BackgroundColor3 = Color3.fromRGB(231, 6, 141):lerp(Color3.new(1,0,0), 0.6); - OnClick = function() - promptBulkPurchase(82180417686588, playerData.isDonor, true) - end + + local catalogFrame = dFrame:Add("TextLabel", { + Text = "Purchase for less in the Roblox catalog"; + Size = UDim2.new(1, -10, 0, 70); + Position = UDim2.new(0, 5, 0, 55); + BackgroundTransparency = 0.25; + TextXAlignment = "Center"; + TextYAlignment = "Top"; + TextWrapped = true; + Children = { + { + Class = "UIPadding"; + PaddingTop = UDim.new(0, 5); + PaddingLeft = UDim.new(0, 5); + PaddingRight = UDim.new(0, 5); + }, + { + Class = "TextButton"; + Name = "Tier1"; + Text = " 300"; + Size = UDim2.new(0.5, -2, 0, 25); + Position = UDim2.new(0, 0, 0, 35); + BackgroundTransparency = 0.5; + BackgroundColor3 = Color3.fromRGB(231, 6, 141); + OnClick = function() + createPurchaseWindow(6878510605, true, playerData.isDonor) + end + }, + { + Class = "TextButton"; + Name = "Tier2"; + Text = " 450"; + Size = UDim2.new(0.5, -2, 0, 25); + Position = UDim2.new(0.5, 2, 0, 35); + BackgroundTransparency = 0.5; + BackgroundColor3 = Color3.fromRGB(231, 6, 141):lerp(Color3.new(1,0,0), 0.33); + OnClick = function() + createPurchaseWindow(120289654999943, true, playerData.isDonor) + end + } + } }) - dFrame:Add("TextButton", { - Text = ` 1000`; - Size = UDim2.new(0, 62, 0, 25); - Position = UDim2.new(0.5, 3, 0, 76); - BackgroundTransparency = 0.5; - BackgroundColor3 = Color3.fromRGB(231, 6, 141):lerp(Color3.new(1,0,0), 1); - OnClick = function() - promptBulkPurchase(113285285080347, playerData.isDonor, true) - end + setLocalPrice(catalogFrame:FindFirstChild("Tier1"), 6878510605) + setLocalPrice(catalogFrame:FindFirstChild("Tier2"), 120289654999943) + + local inGameFrame = dFrame:Add("TextLabel", { + Text = "Purchase in-game, get perks instantly"; + Size = UDim2.new(1, -10, 0, 70); + Position = UDim2.new(0, 5, 0, 130); + BackgroundTransparency = 0.25; + TextXAlignment = "Center"; + TextYAlignment = "Top"; + TextWrapped = true; + Children = { + { + Class = "UIPadding"; + PaddingTop = UDim.new(0, 5); + PaddingLeft = UDim.new(0, 5); + PaddingRight = UDim.new(0, 5); + }, + { + Class = "TextButton"; + Name = "Tier1"; + Text = " 600"; + Size = UDim2.new(0.5, -2, 0, 25); + Position = UDim2.new(0, 0, 0, 35); + BackgroundTransparency = 0.5; + BackgroundColor3 = Color3.fromRGB(231, 6, 141):lerp(Color3.new(1,0,0), 0.66); + OnClick = function() + promptBulkPurchase(82180417686588, playerData.isDonor, true) + end + }, + { + Class = "TextButton"; + Name = "Tier2"; + Text = " 1000"; + Size = UDim2.new(0.5, -2, 0, 25); + Position = UDim2.new(0.5, 2, 0, 35); + BackgroundTransparency = 0.5; + BackgroundColor3 = Color3.new(1,0,0); + OnClick = function() + promptBulkPurchase(113285285080347, playerData.isDonor, true) + end + } + } }) + setLocalPrice(inGameFrame:FindFirstChild("Tier1"), 82180417686588) + setLocalPrice(inGameFrame:FindFirstChild("Tier2"), 113285285080347) + dFrame:Add("Frame", { Size = UDim2.new(1, -10, 0, 1), - Position = UDim2.new(0, 5, 1, -55), + Position = UDim2.new(0, 5, 0, 205), BackgroundColor3 = Color3.new(1, 1, 1), BackgroundTransparency = 0.5; }) @@ -900,15 +1002,16 @@ return function(data, env) dFrame:Add("TextLabel", { Text = "Other (No Perks)"; TextXAlignment = "Center"; - Size = UDim2.new(1, 0, 0, 30); - Position = UDim2.new(0, 5, 1, -55); + Size = UDim2.new(1, -10, 0, 30); + Position = UDim2.new(0, 5, 0, 210); BackgroundTransparency = 1; + Font = Enum.Font.SourceSansBold; }) - dFrame:Add("TextButton", { - Text = ` 50`; + local noPerks1 = dFrame:Add("TextButton", { + Text = ` 75`; Size = UDim2.new(0, 62, 0, 25); - Position = UDim2.new(0, 5, 1, -25); + Position = UDim2.new(0, 5, 0, 240); BackgroundTransparency = 0.7; BackgroundColor3 = Color3.new(0,1,0):lerp(Color3.new(1,0,0), 0.1); OnClick = function() @@ -916,16 +1019,19 @@ return function(data, env) end }) - dFrame:Add("TextButton", { - Text = ` 100`; + local noPerks2 = dFrame:Add("TextButton", { + Text = ` 150`; Size = UDim2.new(0, 62, 0, 25); - Position = UDim2.new(0.5, 3, 1, -25); + Position = UDim2.new(0.5, 3, 0, 240); BackgroundTransparency = 0.5; BackgroundColor3 = Color3.new(0,1,0):lerp(Color3.new(1,0,0), 0.3); OnClick = function() promptBulkPurchase(75165550059686) end }) + + setLocalPrice(noPerks1, 86164337425437) + setLocalPrice(noPerks2, 75165550059686) end diff --git a/MainModule/Server/Core/Variables.luau b/MainModule/Server/Core/Variables.luau index b525302f79..d638f1a470 100644 --- a/MainModule/Server/Core/Variables.luau +++ b/MainModule/Server/Core/Variables.luau @@ -81,7 +81,7 @@ return function(Vargs, GetEnv) BanMessage = "Banned", PlayerJoinFilters = {}; LockMessage = "Not Whitelisted", - DonorPass = {1348327, 1990427, 1911740, 167686, 98593, "6878510605", 5212082, 5212081, "82180417686588", "113285285080347"}, --// Strings are items; numbers are gamepasses + DonorPass = {"6878510605", "82180417686588", "113285285080347", "120289654999943", 1348327, 1990427, 1911740, 167686, 98593, 5212082, 5212081 }, --// Strings are items; numbers are gamepasses WebPanel_Initiated = false, HTTPCheckUrl = "https://www.google.com/humans.txt", IPInfoUrl = "https://speed.cloudflare.com/meta", From ad8e1b9496386d321ddb620131a59e7a56fb6e52 Mon Sep 17 00:00:00 2001 From: joritochip Date: Wed, 23 Sep 2026 02:11:21 -0600 Subject: [PATCH 2/2] Reduce marketplace calls for donor checks --- MainModule/Server/Core/Admin.luau | 49 +++++++++++++++++-- MainModule/Server/Core/Functions.luau | 4 +- MainModule/Server/Core/Process.luau | 3 +- MainModule/Server/Plugins/Debug_Specific.luau | 4 +- MainModule/Server/Plugins/Misc_Features.luau | 22 +++++++++ 5 files changed, 74 insertions(+), 8 deletions(-) diff --git a/MainModule/Server/Core/Admin.luau b/MainModule/Server/Core/Admin.luau index 7bcf2f7315..d38d0867db 100644 --- a/MainModule/Server/Core/Admin.luau +++ b/MainModule/Server/Core/Admin.luau @@ -373,6 +373,11 @@ return function(Vargs, GetEnv) --// How long admin levels will be cached (unless forcibly updated via something like :admin user) AdminLevelCacheTimeout = 30; + --// How long a negative donor check result will be cached (unless cleared by an in-game purchase) + DonorCheckCacheTimeout = 600; + NonDonorCache = {}; + PendingDonorChecks = {}; + CheckSlowMode = function(p: Player) if Admin.SlowMode and Admin.SlowCache[p] and os.time() - Admin.SlowCache[p] < Admin.SlowMode then return true @@ -891,8 +896,27 @@ return function(Vargs, GetEnv) return true end + local lastNegative = Admin.NonDonorCache[key] + if lastNegative and os.time() - lastNegative < Admin.DonorCheckCacheTimeout then + return false + end + + --// Wait on an in-progress check instead of querying every product again + local waiting = Admin.PendingDonorChecks[key] + if waiting then + table.insert(waiting, coroutine.running()) + return coroutine.yield() + end + + waiting = {} + Admin.PendingDonorChecks[key] = waiting + + local isDonor, reliable = false, true for _, pass in Variables.DonorPass do - if p.Parent ~= service.Players then break end + if p.Parent ~= service.Players then + reliable = false + break + end local ran, ret if type(pass) == "number" then @@ -902,15 +926,32 @@ return function(Vargs, GetEnv) end if ran and ret then - Variables.CachedDonors[key] = os.time() - return true + isDonor = true + break + elseif ran == false then + reliable = false end end - return false + if isDonor then + Variables.CachedDonors[key] = os.time() + elseif reliable then + Admin.NonDonorCache[key] = os.time() + end + + Admin.PendingDonorChecks[key] = nil + for _, thread in waiting do + task.spawn(thread, isDonor) + end + + return isDonor end end; + ClearDonorCheckCache = function(p) + Admin.NonDonorCache[tostring(p.UserId)] = nil + end; + CheckBan = function(p) local doCheck = Admin.DoCheck local banCheck = Admin.DoBanCheck diff --git a/MainModule/Server/Core/Functions.luau b/MainModule/Server/Core/Functions.luau index 1980028c0b..690a758904 100644 --- a/MainModule/Server/Core/Functions.luau +++ b/MainModule/Server/Core/Functions.luau @@ -1450,11 +1450,11 @@ return function(Vargs, GetEnv) end; Donor = function(plr) - if Admin.CheckDonor(plr) and Settings.DonorCapes then + if Settings.DonorCapes then local PlayerData = Core.GetPlayer(plr) or {Donor = {}} local donor = PlayerData.Donor or {} - if donor and donor.Enabled then + if donor and donor.Enabled and Admin.CheckDonor(plr) then local img, color, material if donor and donor.Cape then img, color, material = donor.Cape.Image, donor.Cape.Color, donor.Cape.Material diff --git a/MainModule/Server/Core/Process.luau b/MainModule/Server/Core/Process.luau index 8ba5792dbc..82fa794261 100644 --- a/MainModule/Server/Core/Process.luau +++ b/MainModule/Server/Core/Process.luau @@ -479,7 +479,7 @@ return function(Vargs, GetEnv) local pDat = { Player = opts.Player or p; Level = opts.AdminLevel or Admin.GetLevel(p); - isDonor = opts.IsDonor or (Admin.CheckDonor(p) and (Settings.DonorCommands or command.AllowDonors)); + isDonor = opts.IsDonor or ((Settings.DonorCommands or command.AllowDonors) and Admin.CheckDonor(p)); } if opts.isSystem or p == "SYSTEM" then @@ -843,6 +843,7 @@ return function(Vargs, GetEnv) task.delay(1, function() if not service.Players:GetPlayerByUserId(p.UserId) then Core.PlayerData[key] = nil + Admin.ClearDonorCheckCache(p) end end) diff --git a/MainModule/Server/Plugins/Debug_Specific.luau b/MainModule/Server/Plugins/Debug_Specific.luau index a2dcaf793a..694c00c757 100644 --- a/MainModule/Server/Plugins/Debug_Specific.luau +++ b/MainModule/Server/Plugins/Debug_Specific.luau @@ -443,7 +443,9 @@ local service = wrappedEnv.Service local useIds = args[2] and string.lower(args[2]) == "true" for _, v in useIds and string.split(args[1], ",") or service.GetPlayers(plr, args[1]) do - Variables.CachedDonors[tostring(not useIds and v.UserId or tonumber(v))] = nil + local key = tostring(not useIds and v.UserId or tonumber(v)) + Variables.CachedDonors[key] = nil + Admin.NonDonorCache[key] = nil end end }; diff --git a/MainModule/Server/Plugins/Misc_Features.luau b/MainModule/Server/Plugins/Misc_Features.luau index 9b8899a85d..bd87e4ce39 100644 --- a/MainModule/Server/Plugins/Misc_Features.luau +++ b/MainModule/Server/Plugins/Misc_Features.luau @@ -22,6 +22,28 @@ return function(Vargs, GetEnv) end end + -- // Grant donor status on in-game purchases without re-querying MarketplaceService + service.RbxEvent(service.MarketplaceService.PromptBulkPurchaseFinished, function(p, status, results) + if status ~= Enum.MarketplaceBulkPurchasePromptStatus.Completed then return end + + if results and results.Items then + for _, item in results.Items do + if table.find(Variables.DonorPass, tostring(item.id)) then + Variables.CachedDonors[tostring(p.UserId)] = os.time() + break + end + end + end + Admin.ClearDonorCheckCache(p) + end) + + service.RbxEvent(service.MarketplaceService.PromptGamePassPurchaseFinished, function(p, gamepassId, wasPurchased) + if wasPurchased and table.find(Variables.DonorPass, gamepassId) then + Variables.CachedDonors[tostring(p.UserId)] = os.time() + Admin.ClearDonorCheckCache(p) + end + end) + -- // Backwards compatibility local Pcall = server.Pcall local function cPcall(func, ...)