-- SOCIAL screen — friends / party / clan / ranks / chat / invites / moderation
local Players = game:GetService("Players")
local SocialService = game:GetService("SocialService")
local GuiService = game:GetService("GuiService")
local player = Players.LocalPlayer
local gui = Instance.new("ScreenGui")
gui.Name = "SocialScreen"
gui.ResetOnSpawn = false
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.Parent = player:WaitForChild("PlayerGui")
local function base(props, parent)
local f = Instance.new("Frame")
f.BackgroundColor3 = props.bg or Color3.fromRGB(24, 26, 34)
f.BorderSizePixel = 0
for k, v in pairs(props) do
if k ~= "bg" then f[k] = v end
end
f.Parent = parent
return f
end
local function corner(r, parent)
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0, r)
c.Parent = parent
return c
end
local function label(props, parent)
local t = Instance.new("TextLabel")
t.BackgroundTransparency = 1
t.Font = Enum.Font.Gotham
t.TextColor3 = props.color or Color3.fromRGB(235, 238, 245)
t.TextSize = props.size or 14
t.TextXAlignment = props.xalign or Enum.TextXAlignment.Left
t.Text = props.text or ""
for k, v in pairs(props) do
if k ~= "color" and k ~= "size" and k ~= "xalign" and k ~= "text" then t[k] = v end
end
t.Parent = parent
return t
end
local function button(props, parent)
local b = Instance.new("TextButton")
b.BackgroundColor3 = props.bg or Color3.fromRGB(60, 130, 246)
b.BorderSizePixel = 0
b.Font = Enum.Font.GothamBold
b.TextColor3 = props.color or Color3.new(1, 1, 1)
b.TextSize = props.size or 13
b.Text = props.text or ""
for k, v in pairs(props) do
if k ~= "bg" and k ~= "color" and k ~= "size" and k ~= "text" then b[k] = v end
end
b.Parent = parent
corner(6, b)
return b
end
-- Root panel
local root = base({ Name = "Root", Size = UDim2.fromScale(0.62, 0.78), Position = UDim2.fromScale(0.19, 0.11), bg = Color3.fromRGB(20, 22, 30) }, gui)
corner(10, root)
base({ Name = "Accent", Size = UDim2.new(1, 0, 0, 4), bg = Color3.fromRGB(60, 130, 246) }, root)
label({ Text = "Social Hub — Brickyard Tycoon", Size = UDim2.new(1, -24, 0, 40), Position = UDim2.fromOffset(16, 10), size = 20 }, root)
label({ Text = "Friends, party, clan and chat in one place", Size = UDim2.new(1, -24, 0, 18), Position = UDim2.fromOffset(16, 46), size = 13, color = Color3.fromRGB(150, 158, 175) }, root)
-- Tabs
local tabs = base({ Name = "Tabs", Size = UDim2.new(1, -24, 0, 34), Position = UDim2.fromOffset(12, 72), bg = Color3.fromRGB(26, 29, 40) }, root)
corner(8, tabs)
local tabNames = { "Friends", "Party", "Clan", "Ranks", "Chat" }
local tabButtons = {}
for i, name in ipairs(tabNames) do
local b = button({ Text = name, Size = UDim2.new(0.2, -6, 1, -8), Position = UDim2.new((i - 1) * 0.2, 3, 0, 4), bg = i == 1 and Color3.fromRGB(60, 130, 246) or Color3.fromRGB(38, 42, 56) }, tabs)
tabButtons[name] = b
end
local pages = {}
local function makePage(name)
local p = base({ Name = name, Size = UDim2.new(1, -24, 1, -130), Position = UDim2.fromOffset(12, 114), bg = Color3.fromRGB(24, 26, 36), Visible = false }, root)
corner(8, p)
pages[name] = p
return p
end
-- ============ FRIENDS ============
local friendsPage = makePage("Friends")
label({ Text = "Friends not already in your party or clan", Size = UDim2.new(1, -20, 0, 20), Position = UDim2.fromOffset(12, 10), size = 14, color = Color3.fromRGB(150, 158, 175) }, friendsPage)
label({ Text = "Party and clan members are hidden here — find them on their own tabs.", Size = UDim2.new(1, -20, 0, 16), Position = UDim2.fromOffset(12, 30), size = 12, color = Color3.fromRGB(110, 118, 135) }, friendsPage)
-- friend data: presence decides which action exists, not which is greyed
local friendData = {
{ name = "MintBuilder99", presence = "In match — Brickyard Tycoon", inMatch = true, canChat = true },
{ name = "PixelForge_Lia", presence = "In lobby", inMatch = false, canChat = true },
{ name = "StacksOnStacks", presence = "In another experience", inMatch = false, canChat = false },
{ name = "ConveyorKing", presence = "Online — studio", inMatch = false, canChat = true },
}
local friendList = base({ Name = "List", Size = UDim2.new(1, -20, 1, -56), Position = UDim2.fromOffset(10, 50), bg = Color3.fromRGB(28, 31, 44) }, friendsPage)
corner(8, friendList)
local fl = Instance.new("UIListLayout")
fl.Padding = UDim.new(0, 6)
fl.Parent = friendList
local fp = Instance.new("UIPadding")
fp.PaddingTop = UDim.new(0, 8); fp.PaddingLeft = UDim.new(0, 8); fp.PaddingRight = UDim.new(0, 8)
fp.Parent = friendList
for _, fd in ipairs(friendData) do
local row = base({ Size = UDim2.new(1, 0, 0, 52), bg = Color3.fromRGB(36, 40, 56) }, friendList)
corner(6, row)
local dot = base({ Size = UDim2.fromOffset(10, 10), Position = UDim2.fromOffset(12, 21), bg = fd.inMatch and Color3.fromRGB(80, 220, 120) or Color3.fromRGB(150, 158, 175) }, row)
corner(5, dot)
label({ Text = fd.name, Size = UDim2.new(0.4, -30, 0, 20), Position = UDim2.fromOffset(30, 8), size = 15 }, row)
label({ Text = fd.presence, Size = UDim2.new(0.4, -30, 0, 16), Position = UDim2.fromOffset(30, 28), size = 12, color = Color3.fromRGB(150, 158, 175) }, row)
-- Rule 0/2: action exists only when it can succeed. Spectate only in-match; message only when chat is allowed.
if fd.inMatch then
button({ Text = "Spectate", Size = UDim2.fromOffset(92, 32), Position = UDim2.new(1, -104, 0, 10), bg = Color3.fromRGB(80, 220, 120), color = Color3.fromRGB(15, 30, 20) }, row)
elseif fd.canChat then
button({ Text = "Message", Size = UDim2.fromOffset(92, 32), Position = UDim2.new(1, -104, 0, 10), bg = Color3.fromRGB(60, 130, 246) }, row)
end
-- Invite always available via platform prompt (rule 1)
local inv = button({ Text = "Invite", Size = UDim2.fromOffset(92, 32), Position = UDim2.new(1, -204, 0, 10), bg = Color3.fromRGB(38, 42, 56) }, row)
inv.MouseButton1Click:Connect(function()
-- Rule 0: check invite permission, then hand off to the platform prompt
task.spawn(function()
local ok, canInvite = pcall(function()
return SocialService:CanSendGameInviteAsync(fd.name and Players:FindFirstChild(fd.name) or player)
end)
if ok and canInvite then
pcall(function() SocialService:PromptGameInvite(player) end)
else
inv.Text = "N/A here"
task.delay(2, function() inv.Text = "Invite" end)
end
end)
end)
end
-- ============ PARTY ============
local partyPage = makePage("Party")
label({ Text = "Your Party — 2 / 4", Size = UDim2.new(1, -20, 0, 22), Position = UDim2.fromOffset(12, 10), size = 16 }, partyPage)
local partyRows = {
{ name = "You (LocalPlayer)", role = "Party leader", leader = true },
{ name = "MintBuilder99", role = "Member — in your tycoon" },
}
for i, m in ipairs(partyRows) do
local row = base({ Size = UDim2.new(1, -20, 0, 48), Position = UDim2.fromOffset(10, 40 + (i - 1) * 54), bg = Color3.fromRGB(36, 40, 56) }, partyPage)
corner(6, row)
label({ Text = m.name, Size = UDim2.new(0.5, 0, 0, 20), Position = UDim2.fromOffset(12, 6), size = 15 }, row)
label({ Text = m.role, Size = UDim2.new(0.5, 0, 0, 16), Position = UDim2.fromOffset(12, 26), size = 12, color = Color3.fromRGB(150, 158, 175) }, row)
if not m.leader then
button({ Text = "Kick", Size = UDim2.fromOffset(70, 30), Position = UDim2.new(1, -82, 0, 9), bg = Color3.fromRGB(200, 70, 70) }, row)
end
end
button({ Text = "Invite friends (opens Roblox prompt)", Size = UDim2.new(1, -20, 0, 36), Position = UDim2.fromOffset(10, 160), bg = Color3.fromRGB(60, 130, 246) }, partyPage).MouseButton1Click:Connect(function()
task.spawn(function()
local ok, can = pcall(function() return SocialService:CanSendGameInviteAsync(player) end)
if ok and can then pcall(function() SocialService:PromptGameInvite(player) end) end
end)
end)
button({ Text = "Leave party", Size = UDim2.new(1, -20, 0, 36), Position = UDim2.fromOffset(10, 204), bg = Color3.fromRGB(200, 70, 70) }, partyPage)
-- ============ CLAN ============
local clanPage = makePage("Clan")
label({ Text = "Clan: Conveyor Cartel — Level 12", Size = UDim2.new(1, -20, 0, 22), Position = UDim2.fromOffset(12, 10), size = 16 }, clanPage)
local clanRows = {
{ name = "ConveyorKing", rank = "Chief", income = "12,400 / min" },
{ name = "PixelForge_Lia", rank = "Officer", income = "9,850 / min" },
{ name = "You (LocalPlayer)", rank = "Member", income = "6,200 / min" },
{ name = "DropperDan", rank = "Member", income = "4,780 / min" },
}
for i, m in ipairs(clanRows) do
local row = base({ Size = UDim2.new(1, -20, 0, 44), Position = UDim2.fromOffset(10, 40 + (i - 1) * 50), bg = Color3.fromRGB(36, 40, 56) }, clanPage)
corner(6, row)
label({ Text = m.name, Size = UDim2.new(0.34, 0, 1, 0), Position = UDim2.fromOffset(12, 0), size = 14 }, row)
label({ Text = m.rank, Size = UDim2.new(0.22, 0, 1, 0), Position = UDim2.new(0.36, 0, 0, 0), size = 13, color = Color3.fromRGB(120, 200, 255) }, row)
label({ Text = m.income, Size = UDim2.new(0.3, 0, 1, 0), Position = UDim2.new(0.6, 0, 0, 0), size = 13, color = Color3.fromRGB(150, 158, 175) }, row)
end
button({ Text = "Clan tag: [CARTL] — shown next to your chat name", Size = UDim2.new(1, -20, 0, 32), Position = UDim2.fromOffset(10, 246), bg = Color3.fromRGB(38, 42, 56) }, clanPage)
-- ============ RANKS ============
local ranksPage = makePage("Ranks")
label({ Text = "Global Income Ladder — this week", Size = UDim2.new(1, -20, 0, 22), Position = UDim2.fromOffset(12, 10), size = 16 }, ranksPage)
local rankRows = {
{ "#1", "ConveyorKing", "1,204,500" },
{ "#2", "PixelForge_Lia", "986,220" },
{ "#3", "DropperDan", "871,040" },
{ "#41", "You (LocalPlayer)", "312,880" },
}
for i, r in ipairs(rankRows) do
local row = base({ Size = UDim2.new(1, -20, 0, 44), Position = UDim2.fromOffset(10, 40 + (i - 1) * 50), bg = i == 4 and Color3.fromRGB(46, 52, 74) or Color3.fromRGB(36, 40, 56) }, ranksPage)
corner(6, row)
label({ Text = r[1], Size = UDim2.new(0.1, 0, 1, 0), Position = UDim2.fromOffset(12, 0), size = 15, color = Color3.fromRGB(255, 210, 90) }, row)
label({ Text = r[2], Size = UDim2.new(0.5, 0, 1, 0), Position = UDim2.new(0.14, 0, 0, 0), size = 14 }, row)
label({ Text = r[3] .. " cash earned", Size = UDim2.new(0.32, 0, 1, 0), Position = UDim2.new(0.66, 0, 0, 0), size = 13, color = Color3.fromRGB(150, 158, 175) }, row)
end
-- ============ CHAT ============
local chatPage = makePage("Chat")
local chatTabs = { "All", "Clan", "Party" }
local chatTabButtons = {}
for i, name in ipairs(chatTabs) do
local b = button({ Text = name, Size = UDim2.fromOffset(70, 28), Position = UDim2.fromOffset(10 + (i - 1) * 78, 10), bg = i == 1 and Color3.fromRGB(60, 130, 246) or Color3.fromRGB(38, 42, 56) }, chatPage)
chatTabButtons[name] = b
end
local chatLog = base({ Size = UDim2.new(1, -20, 1, -110), Position = UDim2.fromOffset(10, 46), bg = Color3.fromRGB(28, 31, 44) }, chatPage)
corner(8, chatLog)
local chatLines = {
{ tag = "[CARTL]", tagColor = Color3.fromRGB(120, 200, 255), who = "ConveyorKing", msg = "upgraded my second dropper, income is 12k/min now" },
{ tag = "[PARTY]", tagColor = Color3.fromRGB(120, 255, 160), who = "MintBuilder99", msg = "meet at the central park, we can merge plots" },
{ tag = "", tagColor = nil, who = "DropperDan", msg = "anyone selling the golden conveyor skin?" },
{ tag = "[CARTL]", tagColor = Color3.fromRGB(120, 200, 255), who = "PixelForge_Lia", msg = "clan cashout in 20 minutes, get your income up" },
}
for i, line in ipairs(chatLines) do
local t = Instance.new("TextLabel")
t.BackgroundTransparency = 1
t.Size = UDim2.new(1, -16, 0, 18)
t.Position = UDim2.fromOffset(8, 6 + (i - 1) * 22)
t.Font = Enum.Font.Gotham
t.TextSize = 13
t.TextXAlignment = Enum.TextXAlignment.Left
t.RichText = true
t.TextColor3 = Color3.fromRGB(235, 238, 245)
local tagPart = line.tag ~= "" and ('' .. line.tag .. ' ') or ""
t.Text = tagPart .. "" .. line.who .. ": " .. line.msg
t.Parent = chatLog
end
local chatInput = Instance.new("TextBox")
chatInput.BackgroundColor3 = Color3.fromRGB(36, 40, 56)
chatInput.BorderSizePixel = 0
chatInput.Size = UDim2.new(1, -110, 0, 34)
chatInput.Position = UDim2.new(0, 10, 1, -50)
chatInput.Font = Enum.Font.Gotham
chatInput.TextSize = 14
chatInput.PlaceholderText = "Say something to All, Clan or Party…"
chatInput.Text = ""
chatInput.TextColor3 = Color3.fromRGB(235, 238, 245)
chatInput.Parent = chatPage
corner(6, chatInput)
button({ Text = "Send", Size = UDim2.fromOffset(84, 34), Position = UDim2.new(1, -94, 1, -50) }, chatPage)
-- ============ MODERATION ============
local modBar = base({ Name = "Moderation", Size = UDim2.new(1, -24, 0, 34), Position = UDim2.new(0, 12, 1, -44), bg = Color3.fromRGB(26, 29, 40) }, root)
corner(8, modBar)
label({ Text = "Hold a name to report:", Size = UDim2.new(0.3, 0, 1, 0), Position = UDim2.fromOffset(10, 0), size = 12, color = Color3.fromRGB(150, 158, 175) }, modBar)
button({ Text = "Report", Size = UDim2.fromOffset(70, 24), Position = UDim2.new(0.34, 0, 0, 5), bg = Color3.fromRGB(200, 70, 70) }, modBar)
button({ Text = "Block", Size = UDim2.fromOffset(70, 24), Position = UDim2.new(0.34, 78, 0, 5), bg = Color3.fromRGB(120, 60, 60) }, modBar)
button({ Text = "Mute chat", Size = UDim2.fromOffset(84, 24), Position = UDim2.new(0.34, 156, 0, 5), bg = Color3.fromRGB(120, 60, 60) }, modBar)
-- Tab switching
local function showPage(name)
for n, p in pairs(pages) do p.Visible = (n == name) end
for n, b in pairs(tabButtons) do
b.BackgroundColor3 = (n == name) and Color3.fromRGB(60, 130, 246) or Color3.fromRGB(38, 42, 56)
end
end
for n, b in pairs(tabButtons) do
b.MouseButton1Click:Connect(function() showPage(n) end)
end
showPage("Friends")
-- Rule 0 applied live: strip Message buttons for friends the local player cannot contact
task.spawn(function()
for _, fd in ipairs(friendData) do
local target = Players:FindFirstChild(fd.name)
if target then
local ok, can = pcall(function()
return Players:CanUsersDirectChatAsync({ player, target })
end)
if not ok or not can then
local list = friendList:FindFirstChildOfClass("Frame")
for _, row in ipairs(friendList:GetChildren()) do
if row:IsA("Frame") then
local nm = row:FindFirstChildOfClass("TextLabel")
if nm and nm.Text == fd.name then
local msg = nil
for _, c in ipairs(row:GetChildren()) do
if c:IsA("TextButton") and c.Text == "Message" then msg = c end
end
if msg then msg:Destroy() end -- absent, not disabled
end
end
end
end
end
end
end)