How to Make Admin Panel Roblox Studio (Copy & Paste)
This step-by-step guide shows you exactly how to make admin panel roblox studio with a modern UI, global announcements, fly/invisible/invincible toggles, and a server-wide luck system. All three scripts are included below in tidy, scrollable embeds with a one-click copy button.
Target keyword: how to make admin panel roblox studio
What you’ll build
- Clean, scrollable Admin UI
- Global announcement toasts
- Fly / Invisible / Invincible
- Server Luck ×2 with timer HUD
Why it matters
Knowing how to make admin panel roblox studio helps you moderate, test, and manage features quickly without manual commands.
Prerequisites
- Roblox Studio installed
- RemoteEvents & GUI basics
- LocalScripts / Server Scripts
- ModuleScripts, DataStores, MessagingService
Step 1 — RemoteEvents Setup (ReplicatedStorage)
Create these RemoteEvents in ReplicatedStorage:
AdminGlobalMessageGlobalMessageAdminActionLuckUpdateAdminClient
Tip: Exact names are critical when you learn how to make admin panel roblox studio.
Step 2 — AdminPanelClient (LocalScript)
Place at StarterPlayer ➜ StarterPlayerScripts ➜ AdminPanelClient.
-- AdminPanelClient — clean settings UI with scrolling Admin page
-- Features: Announcement, Server Luck, Fly/Invisible/Invincible, Toast, Luck HUD, drag bar, F2 to open
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local player = Players.LocalPlayer
local EVT_SEND_ANN = ReplicatedStorage:WaitForChild("AdminGlobalMessage")
local EVT_BROADCAST = ReplicatedStorage:WaitForChild("GlobalMessage")
local EVT_ADMIN_ACT = ReplicatedStorage:WaitForChild("AdminAction")
local EVT_LUCK_PUSH = ReplicatedStorage:WaitForChild("LuckUpdate")
local EVT_ADMIN_CL = ReplicatedStorage:WaitForChild("AdminClient")
-- ---------- theme ----------
local C = {
bg=Color3.fromRGB(14,14,18), card=Color3.fromRGB(23,23,29),
stroke=Color3.fromRGB(38,38,46), text=Color3.fromRGB(235,238,245),
sub=Color3.fromRGB(160,168,188), blue=Color3.fromRGB(60,110,230),
green=Color3.fromRGB(90,170,95), gray=Color3.fromRGB(120,120,130)
}
local FT = Enum.Font.Gotham
local FB = Enum.Font.GothamBold
-- ---------- toast ----------
local toastGui = Instance.new("ScreenGui")
toastGui.Name, toastGui.IgnoreGuiInset, toastGui.ResetOnSpawn, toastGui.DisplayOrder = "Toast", true, false, 1500
toastGui.Parent = player:WaitForChild("PlayerGui")
local TOAST_W, TOAST_H, TOAST_Y = 520, 58, 0.32
local toast = Instance.new("Frame")
toast.Size = UDim2.new(0,TOAST_W,0,TOAST_H)
toast.Position = UDim2.new(0.5,-TOAST_W/2,TOAST_Y,0)
toast.Visible = false
toast.BackgroundColor3 = C.card
toast.Parent = toastGui
Instance.new("UICorner", toast).CornerRadius = UDim.new(0,10)
do local s=Instance.new("UIStroke",toast) s.Color=C.stroke s.Thickness=1 end
local toastLabel = Instance.new("TextLabel")
toastLabel.BackgroundTransparency = 1
toastLabel.Size = UDim2.new(1,-24,1,0)
toastLabel.Position = UDim2.new(0,12,0,0)
toastLabel.Font = FB; toastLabel.TextScaled = true
toastLabel.TextColor3 = C.text
toastLabel.Parent = toast
local function showToast(text, color)
toastLabel.Text = text; toastLabel.TextColor3 = color or C.text
toast.Visible = true
toast.Position = UDim2.new(0.5,-TOAST_W/2,TOAST_Y+0.03,0)
toast.BackgroundTransparency = 0.35
toastLabel.TextTransparency = 1
TweenService:Create(toast,TweenInfo.new(0.18,Enum.EasingStyle.Quad,Enum.EasingDirection.Out),
{Position=UDim2.new(0.5,-TOAST_W/2,TOAST_Y,0), BackgroundTransparency=0.1}):Play()
TweenService:Create(toastLabel,TweenInfo.new(0.18),{TextTransparency=0}):Play()
task.wait(2)
TweenService:Create(toast,TweenInfo.new(0.18,Enum.EasingStyle.Quad,Enum.EasingDirection.In),
{BackgroundTransparency=0.5}):Play()
TweenService:Create(toastLabel,TweenInfo.new(0.18),{TextTransparency=1}):Play()
task.wait(0.2); toast.Visible=false
end
EVT_BROADCAST.OnClientEvent:Connect(showToast)
-- ---------- main panel ----------
local gui = Instance.new("ScreenGui")
gui.Name, gui.IgnoreGuiInset, gui.ResetOnSpawn, gui.DisplayOrder, gui.Enabled =
"ControlPanel", true, false, 2000, false
gui.Parent = player.PlayerGui
local PANEL_W, PANEL_H = 900, 580
local panel = Instance.new("Frame")
panel.Size = UDim2.new(0,PANEL_W,0,PANEL_H)
panel.Position = UDim2.new(0.5,-PANEL_W/2,0.5,-PANEL_H/2)
panel.BackgroundColor3 = C.bg
panel.Parent = gui
Instance.new("UICorner", panel).CornerRadius = UDim.new(0,12)
do local s=Instance.new("UIStroke",panel) s.Color=C.stroke s.Thickness=1 end
do local p=Instance.new("UIPadding",panel) p.PaddingTop=UDim.new(0,14) p.PaddingLeft=UDim.new(0,14) p.PaddingRight=UDim.new(0,14) end
local title = Instance.new("TextLabel")
title.BackgroundTransparency=1; title.Size=UDim2.new(1,-48,0,42)
title.Text="Settings"; title.Font=FB; title.TextScaled=true; title.TextXAlignment=Enum.TextXAlignment.Left
title.TextColor3=C.text; title.Parent=panel
local closeBtn = Instance.new("TextButton")
closeBtn.Size=UDim2.new(0,36,0,36); closeBtn.Position=UDim2.new(1,-44,0,2)
closeBtn.Text="X"; closeBtn.TextScaled=true; closeBtn.Font=FB
closeBtn.BackgroundColor3=Color3.fromRGB(140,50,50); closeBtn.TextColor3=Color3.new(1,1,1)
Instance.new("UICorner",closeBtn).CornerRadius=UDim.new(0,10)
closeBtn.Parent=panel
closeBtn.MouseButton1Click:Connect(function() gui.Enabled=false end)
-- tabs
local tabs = Instance.new("Frame")
tabs.Position=UDim2.new(0,0,0,52); tabs.Size=UDim2.new(1,0,0,42); tabs.BackgroundTransparency=1; tabs.Parent=panel
local function makeTab(text, x)
local b=Instance.new("TextButton"); b.Size=UDim2.new(0,180,1,0); b.Position=UDim2.new(0,x,0,0)
b.Text=text; b.TextScaled=true; b.Font=FB; b.TextColor3=C.text; b.BackgroundColor3=C.card; b.Parent=tabs
Instance.new("UICorner",b).CornerRadius=UDim.new(0,10)
do local s=Instance.new("UIStroke",b) s.Color=C.stroke s.Thickness=1 end
return b
end
local tabAnn = makeTab("Announcement", 0)
local tabAdmin = makeTab("Admin", 190)
local content = Instance.new("Frame")
content.Position=UDim2.new(0,0,0,100); content.Size=UDim2.new(1,0,1,-128)
content.BackgroundTransparency=1; content.Parent=panel
local pageAnn = Instance.new("Frame"); pageAnn.Size=UDim2.new(1,0,1,0); pageAnn.BackgroundTransparency=1; pageAnn.Parent=content
local pageAdmin = Instance.new("Frame"); pageAdmin.Size=UDim2.new(1,0,1,0); pageAdmin.BackgroundTransparency=1; pageAdmin.Visible=false; pageAdmin.Parent=content
local function showPage(w)
pageAnn.Visible = (w=="ann"); pageAdmin.Visible=(w=="admin")
tabAnn.BackgroundColor3 = (w=="ann") and C.blue or C.card
tabAdmin.BackgroundColor3 = (w=="admin") and C.blue or C.card
end
tabAnn.MouseButton1Click:Connect(function() showPage("ann") end)
tabAdmin.MouseButton1Click:Connect(function() showPage("admin") end)
showPage("ann")
-- drag bar
local dragBar = Instance.new("Frame")
dragBar.Size=UDim2.new(1,0,0,8); dragBar.Position=UDim2.new(0,0,1,-8); dragBar.BackgroundColor3=Color3.new(1,1,1)
dragBar.Parent=panel
local function makeDraggable(handle,target)
local dragging=false; local start; local startPos
local function upd(input)
local d = input.Position - start
target.Position = UDim2.fromOffset(startPos.X.Offset + d.X, startPos.Y.Offset + d.Y)
end
handle.InputBegan:Connect(function(i)
if i.UserInputType==Enum.UserInputType.MouseButton1 or i.UserInputType==Enum.UserInputType.Touch then
dragging=true; start=i.Position; startPos=target.Position
i.Changed:Connect(function() if i.UserInputState==Enum.UserInputState.End then dragging=false end end)
end
end)
handle.InputChanged:Connect(function(i)
if dragging and (i.UserInputType==Enum.UserInputType.MouseMovement or i.UserInputType==Enum.UserInputType.Touch) then
upd(i)
end
end)
end
makeDraggable(dragBar, panel)
-- F2 toggle
UserInputService.InputBegan:Connect(function(input,gpe)
if gpe then return end
if input.KeyCode==Enum.KeyCode.F2 then gui.Enabled = not gui.Enabled end
end)
-- ---------- helpers (cards/rows) ----------
local function card(parent, titleText, height)
local f=Instance.new("Frame"); f.Size=UDim2.new(1,0,0,height); f.BackgroundColor3=C.card; f.Parent=parent
Instance.new("UICorner",f).CornerRadius=UDim.new(0,12)
do local s=Instance.new("UIStroke",f) s.Color=C.stroke s.Thickness=1 end
local pad=Instance.new("UIPadding",f); pad.PaddingTop=UDim.new(0,14); pad.PaddingLeft=UDim.new(0,14); pad.PaddingRight=UDim.new(0,14)
local t=Instance.new("TextLabel"); t.BackgroundTransparency=1; t.Size=UDim2.new(1,0,0,26)
t.Text=titleText; t.TextScaled=true; t.Font=FB; t.TextXAlignment=Enum.TextXAlignment.Left; t.TextColor3=C.text; t.Parent=f
local list=Instance.new("UIListLayout", f); list.Padding=UDim.new(0,10); list.SortOrder=Enum.SortOrder.LayoutOrder
t.LayoutOrder=0
return f
end
local function row(parent, main, sub)
local f=Instance.new("Frame"); f.Name="Row"; f.Size=UDim2.new(1,0,0,64); f.BackgroundColor3=C.card
f.Parent=parent; f.LayoutOrder=1
Instance.new("UICorner",f).CornerRadius=UDim.new(0,10)
do local s=Instance.new("UIStroke",f) s.Color=C.stroke s.Thickness=1 end
local pad=Instance.new("UIPadding",f); pad.PaddingTop=UDim.new(0,12); pad.PaddingLeft=UDim.new(0,12); pad.PaddingRight=UDim.new(0,12)
local left=Instance.new("Frame"); left.BackgroundTransparency=1; left.Size=UDim2.new(1,-260,1,0); left.Parent=f
local title=Instance.new("TextLabel"); title.BackgroundTransparency=1; title.Size=UDim2.new(1,0,0,26)
title.Text=main; title.TextScaled=true; title.Font=FB; title.TextXAlignment=Enum.TextXAlignment.Left; title.TextColor3=C.text; title.Parent=left
local desc=Instance.new("TextLabel"); desc.BackgroundTransparency=1; desc.Position=UDim2.new(0,0,0,26); desc.Size=UDim2.new(1,0,0,20)
desc.Text=sub or ""; desc.TextScaled=true; desc.Font=FT; desc.TextXAlignment=Enum.TextXAlignment.Left; desc.TextColor3=C.sub; desc.Parent=left
local right=Instance.new("Frame"); right.BackgroundTransparency=1; right.Size=UDim2.new(0,240,1,0); right.Position=UDim2.new(1,-240,0,0); right.Parent=f
return f, right
end
local function pill(parent, text, color, cb)
local b=Instance.new("TextButton"); b.Size=UDim2.new(0,120,0,36); b.Position=UDim2.new(1,-120,0.5,-18)
b.BackgroundColor3=color; b.TextColor3=Color3.new(1,1,1); b.TextScaled=true; b.Font=FB; b.Text=text; b.Parent=parent
Instance.new("UICorner",b).CornerRadius=UDim.new(0,18)
b.MouseButton1Click:Connect(function() if cb then cb() end end)
return b
end
-- ---------- Announcement page ----------
do
local a = card(pageAnn, "Announcement", 190)
local r, right = row(a, "Global message", "Broadcast a small popup to all players")
local input = Instance.new("TextBox")
input.Size = UDim2.new(1,-130,0,36) -- BIGGER input
input.Position = UDim2.new(0,0,0.5,-18)
input.BackgroundColor3 = Color3.fromRGB(32,32,40)
input.TextColor3 = C.text; input.PlaceholderText = "type an announcement…"
input.TextScaled = true; input.ClearTextOnFocus = false; input.Font = FT
input.Parent = right
Instance.new("UICorner",input).CornerRadius=UDim.new(0,10)
do local s=Instance.new("UIStroke",input) s.Color=C.stroke s.Thickness=1 end
pill(right, "Send", C.blue, function()
local txt = input.Text
if txt and #txt > 0 then EVT_SEND_ANN:FireServer(txt, nil); input.Text = "" end
end)
end
-- ---------- Admin page (scrollable) ----------
local adminCard = card(pageAdmin, "Admin", 430)
-- make a ScrollingFrame inside the card for many rows
local scroll = Instance.new("ScrollingFrame")
scroll.Size = UDim2.new(1,-0,1,-46)
scroll.Position = UDim2.new(0,0,0,46)
scroll.BackgroundTransparency = 1
scroll.ScrollBarThickness = 6
scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
scroll.CanvasSize = UDim2.new()
scroll.Parent = adminCard
local list = Instance.new("UIListLayout", scroll)
list.Padding = UDim.new(0,10)
list.SortOrder = Enum.SortOrder.LayoutOrder
-- row: Server Luck
do
local r, right = row(scroll, "Server Luck ×2", "Doubles global luck; stacks and resets the 5:00 timer")
pill(right, "Activate", C.green, function() EVT_ADMIN_ACT:FireServer("DoubleLuck") end)
end
-- row: Target player
local targetBox
do
local r, right = row(scroll, "Target player", "Leave blank to target yourself")
targetBox = Instance.new("TextBox")
targetBox.Size = UDim2.new(1,0,0,36)
targetBox.Position = UDim2.new(0,0,0.5,-18)
targetBox.BackgroundColor3 = Color3.fromRGB(32,32,40)
targetBox.TextColor3 = C.text; targetBox.PlaceholderText="name (optional)"
targetBox.TextScaled = true; targetBox.ClearTextOnFocus=false; targetBox.Font=FT
targetBox.Parent = right
Instance.new("UICorner",targetBox).CornerRadius=UDim.new(0,10)
do local s=Instance.new("UIStroke",targetBox) s.Color=C.stroke s.Thickness=1 end
end
-- row: Fly
do
local r, right = row(scroll, "Fly (toggle)", "Grants flight to the target player")
pill(right, "Toggle", C.blue, function()
EVT_ADMIN_ACT:FireServer("FlyToggle", {target = targetBox.Text})
end)
end
-- row: Invisible
do
local r, right = row(scroll, "Invisible (toggle)", "Hide character parts/decals for everyone")
pill(right, "Toggle", C.gray, function()
EVT_ADMIN_ACT:FireServer("InvisibleToggle", {target = targetBox.Text})
end)
end
-- row: Invincible
do
local r, right = row(scroll, "Invincible (toggle)", "Locks Health to MaxHealth")
pill(right, "Toggle", C.green, function()
EVT_ADMIN_ACT:FireServer("InvincibleToggle", {target = targetBox.Text})
end)
end
-- ---------- Luck HUD ----------
local hud = Instance.new("ScreenGui")
hud.Name="LuckHUD"; hud.IgnoreGuiInset=true; hud.ResetOnSpawn=false; hud.DisplayOrder=1100; hud.Parent=player.PlayerGui
local hudFrame = Instance.new("Frame")
hudFrame.Size=UDim2.new(0,210,0,60); hudFrame.Position=UDim2.new(1,-220,1,-70)
hudFrame.BackgroundColor3=C.card; hudFrame.Visible=false; hudFrame.Parent=hud
Instance.new("UICorner",hudFrame).CornerRadius=UDim.new(0,10)
do local s=Instance.new("UIStroke",hudFrame) s.Color=C.stroke s.Thickness=1 end
local hudLabel = Instance.new("TextLabel")
hudLabel.BackgroundTransparency=1; hudLabel.Size=UDim2.new(1,-16,0,24); hudLabel.Position=UDim2.new(0,8,0,6)
hudLabel.Font=FB; hudLabel.TextScaled=true; hudLabel.TextColor3=Color3.fromRGB(120,220,120); hudLabel.Text="luck"; hudLabel.Parent=hudFrame
local hudTimer = Instance.new("TextLabel")
hudTimer.BackgroundTransparency=1; hudTimer.Size=UDim2.new(1,-16,0,22); hudTimer.Position=UDim2.new(0,8,0,32)
hudTimer.Font=FT; hudTimer.TextScaled=true; hudTimer.TextColor3=C.text; hudTimer.Text="00:00"; hudTimer.Parent=hudFrame
local currentMult, secondsLeft, lastTick = 1, 0, 0
local function fmtTime(s) s = math.max(0, math.floor(s)); return string.format("%02d:%02d", math.floor(s/60), s%60) end
local function refreshHUD()
if secondsLeft > 0 and currentMult > 1 then
hudFrame.Visible = true
hudLabel.Text = ("luck x%d"):format(currentMult)
hudTimer.Text = fmtTime(secondsLeft)
else
hudFrame.Visible = false
end
end
EVT_LUCK_PUSH.OnClientEvent:Connect(function(mult, secs) currentMult, secondsLeft = mult, secs; refreshHUD() end)
RunService.RenderStepped:Connect(function(dt)
if secondsLeft > 0 then
secondsLeft = math.max(0, secondsLeft - dt)
if math.floor(secondsLeft) ~= lastTick then lastTick = math.floor(secondsLeft); refreshHUD() end
end
end)
-- ---------- Local Fly controller ----------
local flying=false; local lv,att,ao; local move=Vector3.zero; local up,down=0,0
local function stopFly()
flying=false; if lv then lv:Destroy(); lv=nil end; if ao then ao:Destroy(); ao=nil end; if att then att:Destroy(); att=nil end
local ch=player.Character; if ch then local h=ch:FindFirstChildOfClass("Humanoid"); if h then h.PlatformStand=false end end
end
local function startFly()
local ch=player.Character or player.CharacterAdded:Wait()
local hrp=ch:WaitForChild("HumanoidRootPart"); local hum=ch:WaitForChild("Humanoid")
att=Instance.new("Attachment",hrp)
lv=Instance.new("LinearVelocity",hrp); lv.Attachment0=att; lv.MaxForce=1e6; lv.VelocityConstraintMode=Enum.VelocityConstraintMode.Vector
ao=Instance.new("AlignOrientation",hrp); ao.Mode=Enum.OrientationAlignmentMode.OneAttachment; ao.Attachment0=att; ao.MaxTorque=math.huge; ao.ReactionTorqueEnabled=true
hum.PlatformStand=true; flying=true
end
UserInputService.InputBegan:Connect(function(i,gpe)
if gpe then return end
if i.KeyCode==Enum.KeyCode.W then move=Vector3.new(move.X,move.Y,-1)
elseif i.KeyCode==Enum.KeyCode.S then move=Vector3.new(move.X,move.Y,1)
elseif i.KeyCode==Enum.KeyCode.A then move=Vector3.new(-1,move.Y,move.Z)
elseif i.KeyCode==Enum.KeyCode.D then move=Vector3.new(1,move.Y,move.Z)
elseif i.KeyCode==Enum.KeyCode.Space then up=1
elseif i.KeyCode==Enum.KeyCode.LeftControl then down=1 end
end)
UserInputService.InputEnded:Connect(function(i,gpe)
if i.KeyCode==Enum.KeyCode.W or i.KeyCode==Enum.KeyCode.S then move=Vector3.new(move.X,move.Y,0)
elseif i.KeyCode==Enum.KeyCode.A or i.KeyCode==Enum.KeyCode.D then move=Vector3.new(0,move.Y,move.Z)
elseif i.KeyCode==Enum.KeyCode.Space then up=0
elseif i.KeyCode==Enum.KeyCode.LeftControl then down=0 end
end)
RunService.RenderStepped:Connect(function(dt)
if not flying then return end
local ch=player.Character; if not ch then return end
local hrp=ch:FindFirstChild("HumanoidRootPart"); if not hrp then return end
local cam=workspace.CurrentCamera; local cf=CFrame.new(Vector3.zero, cam.CFrame.LookVector)
local dir=(cf.RightVector*move.X + cf.LookVector*(-move.Z)); local vert=up-down
local speed=60; lv.VectorVelocity=dir*speed + Vector3.new(0,vert*speed,0)
ao.CFrame=CFrame.new(Vector3.zero, (dir.Magnitude>0.001 and dir.Unit or cam.CFrame.LookVector))
end)
EVT_ADMIN_CL.OnClientEvent:Connect(function(action)
if action=="FlyToggle" then
if flying then stopFly() else startFly() end
showToast(flying and "fly: enabled" or "fly: disabled", flying and Color3.fromRGB(120,220,120) or Color3.fromRGB(220,120,120))
end
end)
Step 3 — AdminPanelServer (ServerScriptService)
Create a Script named AdminPanelServer in ServerScriptService.
Step 4 — LuckManager (ModuleScript)
Add a ModuleScript named LuckManager in ServerScriptService.
-- ServerScriptService/LuckManager (ModuleScript)
-- Global luck that doubles on demand and resets to 5:00. Cross-server in live games,
-- safe no-op for Studio (no DataStore/Messaging errors).
local RunService = game:GetService("RunService")
local MessagingService = game:GetService("MessagingService")
local DataStoreService = game:GetService("DataStoreService")
local LUCK_TOPIC = "GLOBAL_LUCK_V1"
local LUCK_DS = DataStoreService:GetDataStore("LuckStateV1")
local DEFAULT_MULT = 1
local DURATION_SECS = 5 * 60 -- 5 minutes
local IS_STUDIO = RunService:IsStudio()
local State = { multiplier = DEFAULT_MULT, expiresAt = 0 } -- os.time()
local Subscribers = {}
local function now() return os.time() end
local function secondsRemaining() return math.max(0, State.expiresAt - now()) end
local function pushLocal()
for _, cb in ipairs(Subscribers) do
task.spawn(cb, State.multiplier, secondsRemaining())
end
end
local function applyState(mult, exp)
State.multiplier = mult
State.expiresAt = exp
pushLocal()
end
local function persist()
if IS_STUDIO then return end
local ok, err = pcall(function()
LUCK_DS:SetAsync("state", { multiplier = State.multiplier, expiresAt = State.expiresAt })
end)
if not ok then warn("[Luck] Persist failed:", err) end
end
local function publish()
if IS_STUDIO then return end
local ok, err = pcall(function()
MessagingService:PublishAsync(LUCK_TOPIC, {
multiplier = State.multiplier,
expiresAt = State.expiresAt,
t = now(),
})
end)
if not ok then warn("[Luck] Publish failed:", err) end
end
local function load()
if IS_STUDIO then
applyState(DEFAULT_MULT, 0)
return
end
local ok, data = pcall(function() return LUCK_DS:GetAsync("state") end)
if ok and typeof(data) == "table" then
applyState(tonumber(data.multiplier) or DEFAULT_MULT, tonumber(data.expiresAt) or 0)
else
applyState(DEFAULT_MULT, 0)
end
end
local function subscribe()
if IS_STUDIO then return end
local ok, sub = pcall(function()
return MessagingService:SubscribeAsync(LUCK_TOPIC, function(msg)
local d = msg.Data
if typeof(d) ~= "table" then return end
if typeof(d.multiplier) ~= "number" or typeof(d.expiresAt) ~= "number" then return end
applyState(d.multiplier, d.expiresAt)
end)
end)
if not ok then warn("[Luck] Subscribe failed:", sub) end
end
local M = {}
function M.Init()
load()
subscribe()
end
function M.OnChanged(cb)
table.insert(Subscribers, cb)
task.defer(cb, State.multiplier, secondsRemaining())
end
function M.Get()
return State.multiplier, secondsRemaining()
end
function M.DoubleAndReset()
local newMult = math.clamp(State.multiplier * 2, 1, 2^30)
local newExp = now() + DURATION_SECS
applyState(newMult, newExp)
persist()
publish()
end
function M.Tick()
if secondsRemaining() <= 0 and State.multiplier ~= DEFAULT_MULT then
applyState(DEFAULT_MULT, 0)
persist()
publish()
end
end
return M
Testing Checklist
- Press F2 to open/close the panel
- Send a global message (toast shows on screen)
- Toggle Fly, Invisible, Invincible
- Activate Server Luck ×2 and watch the HUD timer
- Target by partial player name
- Confirm the Admin page scrolls fully
Important: Live servers handle DataStores differently. Always test publish/subscribe when you’re serious about how to make admin panel roblox studio.
Restricting Access
Add a simple whitelist in the server script:
local ADMIN_USERIDS = { [123456789]=true, [987654321]=true }
-- before executing actions:
-- if not ADMIN_USERIDS[player.UserId] then return end















