-- Services
local CoreGui = game:GetService("CoreGui")
local Players = game:GetService("Players")
local StarterGui = game:GetService("StarterGui")
local UserInputService = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local TeleportService = game:GetService("TeleportService")
local LocalPlayer = Players.LocalPlayer
---------------------------------------------------------
-- [ RP & BIO SYSTEM ]
---------------------------------------------------------
local function UpdateRP()
pcall(function()
local RE = game:GetService("ReplicatedStorage"):WaitForChild("RE", 5)
if RE then
RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayName", "ȤeNix CHaT")
RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayBio", "By ZaIN, J00")
RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPNameColor", Color3.fromRGB(170, 85, 255))
pcall(function()
RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPBioColor", Color3.fromRGB(0, 100, 0))
end)
end
end)
end
task.spawn(function()
task.wait(5)
local RE = game:GetService("ReplicatedStorage"):WaitForChild("RE", 5)
if RE then
RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayName", "ZeNiX CHaT π foR aLL")
RE:FindFirstChild("1RPNam1eTex1t"):FireServer("RolePlayBio", "By ZaIN, J00")
local Green = Color3.fromRGB(0, 255, 0)
local Red = Color3.fromRGB(255, 0, 0)
RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPNameColor", Green)
RE:FindFirstChild("1RPNam1eColo1r"):FireServer("PickingRPBioColor", Red)
end
end)
---------------------------------------------------------
-- CONFIGURATION
local FIREBASE_URL = "https://betterchat-rblx-default-rtdb.firebaseio.com/"
local CURRENT_VERSION_STRING = "2"
local FOLDER_NAME = "BetterChat"
local VERSION_FILE = FOLDER_NAME .. "/version.txt"
local RUNS_FILE = FOLDER_NAME .. "/runs.txt"
local CHAT_PATH = "GlobalChat/Servers/" .. (game.JobId ~= "" and game.JobId or "Studio_Debug")
local PRESENCE_PATH = "GlobalChat/OnlinePlayers"
local ANNOUNCE_PATH = "GlobalChat/Announcement"
local MAX_MESSAGES = 50
local PRESENCE_TTL = 90
-- FIX 1: safeRequest no longer falls back to HttpService
local function safeRequest(options)
local req = (syn and syn.request) or (http and http.request) or http_request or request
if req then
return req(options)
else
return {Success = false, Body = ""}
end
end
local function sanitize(s)
return tostring(s):gsub("[<>&]", {["<"] = "<", [">"] = ">", ["&"] = "&"})
end
-- 1. BOOTSTRAPPER
if not isfolder(FOLDER_NAME) then makefolder(FOLDER_NAME) end
local function CheckForUpdates()
local response = safeRequest({
Url = FIREBASE_URL .. "GlobalChat/LatestVersion.json",
Method = "GET"
})
if not response.Success then return false end
if response.Body ~= "null" then
local cloudVersion = string.gsub(response.Body, '["%s]', '')
if CURRENT_VERSION_STRING ~= cloudVersion then return false end
end
return true
end
if not CheckForUpdates() then
local parentGui = (gethui and gethui()) or CoreGui:FindFirstChild("RobloxGui") or CoreGui
local UpdateGui = Instance.new("ScreenGui", parentGui)
UpdateGui.Name = "BetterChatUpdater"
local BG = Instance.new("Frame", UpdateGui)
BG.Size = UDim2.new(1, 0, 1, 0)
BG.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
local Warning = Instance.new("TextLabel", BG)
Warning.Size = UDim2.new(1, 0, 1, 0)
Warning.Text = "Script outdated, please update."
Warning.TextColor3 = Color3.fromRGB(255, 80, 80)
Warning.TextSize = 30
Warning.BackgroundTransparency = 1
return
end
writefile(VERSION_FILE, CURRENT_VERSION_STRING)
-- 2. RUN COUNTER
local runCount = 0
if isfile(RUNS_FILE) then
runCount = tonumber(readfile(RUNS_FILE)) or 0
end
runCount = runCount + 1
writefile(RUNS_FILE, tostring(runCount))
local isFirstRun = runCount == 1
-- 3. PRESENCE SYSTEM
local MY_JOB_ID = game.JobId ~= "" and game.JobId or "Studio_Debug"
local MY_PLACE_ID = game.PlaceId
local function WritePresence()
safeRequest({
Url = FIREBASE_URL .. PRESENCE_PATH .. "/" .. LocalPlayer.UserId .. ".json",
Method = "PUT",
Headers = {["Content-Type"] = "application/json"},
Body = HttpService:JSONEncode({
DisplayName = LocalPlayer.DisplayName,
Username = LocalPlayer.Name,
UserId = LocalPlayer.UserId,
JobId = MY_JOB_ID,
PlaceId = MY_PLACE_ID,
LastSeen = os.time()
})
})
end
local function ClearPresence()
safeRequest({
Url = FIREBASE_URL .. PRESENCE_PATH .. "/" .. LocalPlayer.UserId .. ".json",
Method = "DELETE"
})
end
WritePresence()
task.spawn(function()
while task.wait(30) do WritePresence() end
end)
LocalPlayer.AncestryChanged:Connect(function()
if not LocalPlayer:IsDescendantOf(game) then ClearPresence() end
end)
-- 4. UI SETUP
local parentGui = (gethui and gethui()) or CoreGui:FindFirstChild("RobloxGui") or CoreGui
if parentGui:FindFirstChild("GlobalChatGui") then parentGui.GlobalChatGui:Destroy() end
local ScreenGui = Instance.new("ScreenGui", parentGui)
ScreenGui.Name = "GlobalChatGui"
ScreenGui.IgnoreGuiInset = true
ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
local Container = Instance.new("Frame", ScreenGui)
Container.Name = "mainframe"
Container.BackgroundColor3 = Color3.new(0, 0, 0)
Container.BackgroundTransparency = 0.2
Container.BorderColor3 = Color3.new(0, 0, 0)
Container.BorderSizePixel = 0
Container.Position = UDim2.new(0, 10, 0, 60)
Container.Size = UDim2.new(0, 340, 0, 202)
Container.Visible = true
local ContainerCorner = Instance.new("UICorner", Container)
ContainerCorner.CornerRadius = UDim.new(0, 9)
local ChatScroll = Instance.new("ScrollingFrame", Container)
ChatScroll.BackgroundTransparency = 1
ChatScroll.Position = UDim2.new(0, 8, 0, 6)
ChatScroll.Size = UDim2.new(1, -16, 1, -48)
ChatScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
ChatScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
ChatScroll.ScrollBarThickness = 2
ChatScroll.ScrollBarImageColor3 = Color3.fromRGB(255, 255, 255)
local Layout = Instance.new("UIListLayout", ChatScroll)
Layout.Padding = UDim.new(0, 4)
local TextBox = Instance.new("TextBox", Container)
TextBox.BackgroundColor3 = Color3.new(0, 0, 0)
TextBox.BackgroundTransparency = 0.5
TextBox.BorderColor3 = Color3.new(0, 0, 0)
TextBox.BorderSizePixel = 0
TextBox.Position = UDim2.new(0.0234042555, 0, 0.797029674, 0)
TextBox.Size = UDim2.new(0, 300, 0, 33)
TextBox.Font = Enum.Font.SourceSansBold
TextBox.PlaceholderText = "To chat, click here"
TextBox.Text = ""
TextBox.TextColor3 = Color3.new(1, 1, 1)
TextBox.TextSize = 23
TextBox.TextWrapped = true
TextBox.TextXAlignment = Enum.TextXAlignment.Left
TextBox.ClearTextOnFocus = false
Instance.new("UICorner", TextBox)
local ToggleButton = Instance.new("ImageButton", ScreenGui)
ToggleButton.Name = "ChatToggle"
ToggleButton.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
ToggleButton.BackgroundTransparency = 0.5
ToggleButton.AnchorPoint = Vector2.new(1, 0)
ToggleButton.Position = UDim2.new(1, -50, 0, 4)
ToggleButton.Size = UDim2.new(0, 32, 0, 32)
ToggleButton.Image = "rbxasset://textures/ui/Chat/Chat.png"
Instance.new("UICorner", ToggleButton).CornerRadius = UDim.new(0, 8)
local UnreadBadge = Instance.new("TextLabel", ScreenGui)
UnreadBadge.Name = "UnreadBadge"
UnreadBadge.BackgroundColor3 = Color3.fromRGB(255, 60, 60)
UnreadBadge.AnchorPoint = Vector2.new(1, 0)
UnreadBadge.Position = UDim2.new(1, -44, 0, 34)
UnreadBadge.Size = UDim2.new(0, 18, 0, 14)
UnreadBadge.Font = Enum.Font.GothamBold
UnreadBadge.Text = ""
UnreadBadge.TextColor3 = Color3.fromRGB(255, 255, 255)
UnreadBadge.TextSize = 9
UnreadBadge.TextXAlignment = Enum.TextXAlignment.Center
UnreadBadge.Visible = false
Instance.new("UICorner", UnreadBadge).CornerRadius = UDim.new(0, 4)
local OnlineButton = Instance.new("TextButton", ScreenGui)
OnlineButton.Name = "OnlineToggle"
OnlineButton.BackgroundColor3 = Color3.fromRGB(0, 180, 100)
OnlineButton.BackgroundTransparency = 0.4
OnlineButton.AnchorPoint = Vector2.new(1, 0)
OnlineButton.Position = UDim2.new(1, -88, 0, 4)
OnlineButton.Size = UDim2.new(0, 32, 0, 32)
OnlineButton.Text = "👥"
OnlineButton.TextSize = 18
Instance.new("UICorner", OnlineButton).CornerRadius = UDim.new(0, 8)
local OnlinePanel = Instance.new("Frame", ScreenGui)
OnlinePanel.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
OnlinePanel.BackgroundTransparency = 0.5
OnlinePanel.AnchorPoint = Vector2.new(0.5, 0.5)
OnlinePanel.Position = UDim2.new(0.5, 0, 0.5, 0)
OnlinePanel.Size = UDim2.new(0, 360, 0, 180)
OnlinePanel.Visible = false
Instance.new("UICorner", OnlinePanel)
local OnlineTitle = Instance.new("TextLabel", OnlinePanel)
OnlineTitle.BackgroundTransparency = 1
OnlineTitle.Position = UDim2.new(0, 8, 0, 4)
OnlineTitle.Size = UDim2.new(1, -16, 0, 20)
OnlineTitle.Font = Enum.Font.GothamBold
OnlineTitle.Text = "🟢 Online Players"
OnlineTitle.TextColor3 = Color3.fromRGB(100, 255, 150)
OnlineTitle.TextSize = 14
OnlineTitle.TextXAlignment = Enum.TextXAlignment.Left
local OnlineScroll = Instance.new("ScrollingFrame", OnlinePanel)
OnlineScroll.BackgroundTransparency = 1
OnlineScroll.Position = UDim2.new(0, 8, 0, 28)
OnlineScroll.Size = UDim2.new(1, -16, 1, -36)
OnlineScroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
OnlineScroll.CanvasSize = UDim2.new(0, 0, 0, 0)
OnlineScroll.ScrollBarThickness = 2
local OnlineLayout = Instance.new("UIListLayout", OnlineScroll)
OnlineLayout.Padding = UDim.new(0, 4)
-- 5. UNREAD LOGIC
local unreadCount = 0
local function AddUnread()
if Container.Visible then return end
unreadCount += 1
UnreadBadge.Text = unreadCount > 99 and "99+" or tostring(unreadCount)
UnreadBadge.Visible = true
end
local function ClearUnread()
unreadCount = 0
UnreadBadge.Text = ""
UnreadBadge.Visible = false
end
-- 6. CORE CHAT LOGIC
local function trimMessages()
local labels = {}
for _, c in ipairs(ChatScroll:GetChildren()) do
if c:IsA("TextLabel") then table.insert(labels, c) end
end
table.sort(labels, function(a, b)
return a.AbsolutePosition.Y < b.AbsolutePosition.Y
end)
if #labels > MAX_MESSAGES then
for i = 1, #labels - MAX_MESSAGES do
labels[i]:Destroy()
end
end
end
local function CreateMessage(sender, text, isSystem)
local TextLabel = Instance.new("TextLabel", ChatScroll)
TextLabel.BackgroundTransparency = 1
TextLabel.Size = UDim2.new(1, 0, 0, 0)
TextLabel.Font = Enum.Font.SourceSansBold
TextLabel.TextSize = 16
TextLabel.RichText = true
TextLabel.TextWrapped = true
TextLabel.AutomaticSize = Enum.AutomaticSize.Y
TextLabel.TextXAlignment = Enum.TextXAlignment.Left
local nameColor = isSystem and "rgb(255, 80, 80)" or "rgb(150, 200, 255)"
TextLabel.Text = string.format(
'%s: %s',
nameColor, sanitize(sender), sanitize(text)
)
trimMessages()
task.defer(function()
ChatScroll.CanvasPosition = Vector2.new(0, ChatScroll.AbsoluteCanvasSize.Y)
end)
if not isSystem then AddUnread() end
end
local function CreateAnnouncement(sender, text)
local toast = Instance.new("Frame", ScreenGui)
toast.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
toast.AnchorPoint = Vector2.new(0.5, 0)
toast.Position = UDim2.new(0.5, 0, 0, 10)
toast.Size = UDim2.new(0, 320, 0, 80)
Instance.new("UICorner", toast).CornerRadius = UDim.new(1, 0)
local leftBulge = Instance.new("Frame", toast)
leftBulge.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
leftBulge.BorderSizePixel = 0
leftBulge.AnchorPoint = Vector2.new(0.5, 0.5)
leftBulge.Position = UDim2.new(0, 18, 0.5, 0)
leftBulge.Size = UDim2.new(0, 72, 0, 72)
Instance.new("UICorner", leftBulge).CornerRadius = UDim.new(1, 0)
local rightBulge = Instance.new("Frame", toast)
rightBulge.BackgroundColor3 = Color3.fromRGB(130, 130, 130)
rightBulge.BorderSizePixel = 0
rightBulge.AnchorPoint = Vector2.new(0.5, 0.5)
rightBulge.Position = UDim2.new(1, -18, 0.5, 0)
rightBulge.Size = UDim2.new(0, 72, 0, 72)
Instance.new("UICorner", rightBulge).CornerRadius = UDim.new(1, 0)
local senderLabel = Instance.new("TextLabel", toast)
senderLabel.BackgroundTransparency = 1
senderLabel.AnchorPoint = Vector2.new(0.5, 0.5)
senderLabel.Position = UDim2.new(0.5, 0, 0.35, 0)
senderLabel.Size = UDim2.new(0.8, 0, 0, 22)
senderLabel.Font = Enum.Font.GothamMedium
senderLabel.Text = sanitize(sender)
senderLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
senderLabel.TextSize = 14
senderLabel.TextStrokeTransparency = 0
senderLabel.TextStrokeColor3 = Color3.fromRGB(0, 0, 0)
senderLabel.TextXAlignment = Enum.TextXAlignment.Center
local msgLabel = Instance.new("TextLabel", toast)
msgLabel.BackgroundTransparency = 1
msgLabel.AnchorPoint = Vector2.new(0.5, 0.5)
msgLabel.Position = UDim2.new(0.5, 0, 0.68, 0)
msgLabel.Size = UDim2.new(0.8, 0, 0, 28)
msgLabel.Font = Enum.Font.GothamBold
msgLabel.Text = sanitize(text)
msgLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
msgLabel.TextSize = 20
msgLabel.TextStrokeTransparency = 0
msgLabel.TextStrokeColor3 = Color3.fromRGB(0, 0, 0)
msgLabel.TextXAlignment = Enum.TextXAlignment.Center
msgLabel.TextWrapped = true
local chatLabel = Instance.new("TextLabel", ChatScroll)
chatLabel.BackgroundColor3 = Color3.fromRGB(255, 200, 0)
chatLabel.BackgroundTransparency = 0.85
chatLabel.Size = UDim2.new(1, 0, 0, 0)
chatLabel.Font = Enum.Font.SourceSansBold
chatLabel.TextSize = 16
chatLabel.RichText = true
chatLabel.TextWrapped = true
chatLabel.AutomaticSize = Enum.AutomaticSize.Y
chatLabel.TextXAlignment = Enum.TextXAlignment.Left
Instance.new("UICorner", chatLabel).CornerRadius = UDim.new(0, 4)
chatLabel.Text = string.format(
'📢 %s: %s',
sanitize(sender), sanitize(text)
)
trimMessages()
task.defer(function()
ChatScroll.CanvasPosition = Vector2.new(0, ChatScroll.AbsoluteCanvasSize.Y)
end)
task.delay(6, function()
if toast and toast.Parent then toast:Destroy() end
end)
end
local function SendToFirebase(sender, message, msgId)
safeRequest({
Url = FIREBASE_URL .. CHAT_PATH .. ".json",
Method = "POST",
Headers = {["Content-Type"] = "application/json"},
Body = HttpService:JSONEncode({
["Sender"] = sender,
["Message"] = message,
["Time"] = os.time(),
["MsgId"] = msgId
})
})
end
local seenKeys = {}
-- 7. ANNOUNCEMENT LOGIC
local seenAnnouncements = {}
local function SeedAnnouncements()
local response = safeRequest({
Url = FIREBASE_URL .. ANNOUNCE_PATH .. ".json",
Method = "GET"
})
if response.Success and response.Body ~= "null" then
local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
if ok then
for key, _ in pairs(data) do
seenAnnouncements[key] = true
end
end
end
end
local function CheckAnnouncement()
local response = safeRequest({
Url = FIREBASE_URL .. ANNOUNCE_PATH .. ".json",
Method = "GET"
})
if not response.Success or response.Body == "null" then return end
local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
if not ok then return end
local entries = {}
for key, v in pairs(data) do
if not seenAnnouncements[key] then
table.insert(entries, {key = key, data = v})
end
end
table.sort(entries, function(a, b) return a.data.PostedAt < b.data.PostedAt end)
for _, entry in ipairs(entries) do
seenAnnouncements[entry.key] = true
CreateAnnouncement(entry.data.PostedBy or "Owner", entry.data.Message or "")
end
end
local function ShowBubble(senderName, text)
local target = nil
for _, p in ipairs(Players:GetPlayers()) do
if p.DisplayName == senderName or p.Name == senderName then
target = p
break
end
end
if not target or not target.Character then return end
local head = target.Character:FindFirstChild("Head")
if not head then return end
local existing = head:FindFirstChild("BetterChatBubble")
if existing then existing:Destroy() end
local billboard = Instance.new("BillboardGui", head)
billboard.Name = "BetterChatBubble"
billboard.Size = UDim2.new(0, 200, 0, 50)
billboard.StudsOffset = Vector3.new(0, 2.5, 0)
billboard.AlwaysOnTop = false
billboard.ResetOnSpawn = false
local bg = Instance.new("Frame", billboard)
bg.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
bg.BackgroundTransparency = 0.1
bg.Size = UDim2.new(1, 0, 1, 0)
bg.BorderSizePixel = 0
Instance.new("UICorner", bg).CornerRadius = UDim.new(0, 8)
local label = Instance.new("TextLabel", bg)
label.BackgroundTransparency = 1
label.Size = UDim2.new(1, -10, 1, 0)
label.Position = UDim2.new(0, 5, 0, 0)
label.Font = Enum.Font.GothamMedium
label.TextSize = 13
label.TextColor3 = Color3.fromRGB(0, 0, 0)
label.TextWrapped = true
label.Text = text
label.TextXAlignment = Enum.TextXAlignment.Center
billboard.Size = UDim2.new(0, 200, 0, math.clamp(label.TextBounds.Y + 16, 36, 80))
task.delay(5, function()
if billboard and billboard.Parent then
billboard:Destroy()
end
end)
end
-- FIX 2: StartPolling Optimized for Instant Chat
local function StartPolling()
task.spawn(function()
while ScreenGui.Parent and task.wait(2.5) do
local response = safeRequest({
Url = FIREBASE_URL .. CHAT_PATH .. ".json?orderBy=\"Time\"&limitToLast=10",
Method = "GET"
})
if response.Success and response.Body ~= "null" then
local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
if not ok then continue end
local msgs = {}
for key, v in pairs(data) do
local uid = v.MsgId or key -- استخدام المعرف الفريد إن وجد
if not seenKeys[uid] then
table.insert(msgs, {uid = uid, data = v})
end
end
table.sort(msgs, function(a, b) return a.data.Time < b.data.Time end)
for _, entry in ipairs(msgs) do
seenKeys[entry.uid] = true
CreateMessage(entry.data.Sender, entry.data.Message, false)
ShowBubble(entry.data.Sender, entry.data.Message)
end
end
end
end)
task.spawn(function()
while ScreenGui.Parent and task.wait(5) do
CheckAnnouncement()
end
end)
end
-- 8. ONLINE PLAYERS LOGIC
local function ClearOnlineList()
for _, c in ipairs(OnlineScroll:GetChildren()) do
if not c:IsA("UIListLayout") then c:Destroy() end
end
end
local function BuildPlayerRow(info)
local row = Instance.new("Frame", OnlineScroll)
row.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
row.BackgroundTransparency = 0.9
row.Size = UDim2.new(1, 0, 0, 32)
Instance.new("UICorner", row)
local nameLabel = Instance.new("TextLabel", row)
nameLabel.BackgroundTransparency = 1
nameLabel.Position = UDim2.new(0, 8, 0, 0)
nameLabel.Size = UDim2.new(1, -90, 1, 0)
nameLabel.Font = Enum.Font.GothamMedium
nameLabel.TextSize = 13
nameLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
nameLabel.TextXAlignment = Enum.TextXAlignment.Left
nameLabel.TextTruncate = Enum.TextTruncate.AtEnd
nameLabel.RichText = true
local sameServer = info.JobId == MY_JOB_ID
local tag = sameServer and " [Here]" or ""
nameLabel.Text = sanitize(info.DisplayName) .. " (@" .. sanitize(info.Username) .. ")" .. tag
if not sameServer then
local joinBtn = Instance.new("TextButton", row)
joinBtn.BackgroundColor3 = Color3.fromRGB(0, 180, 100)
joinBtn.BackgroundTransparency = 0.3
joinBtn.Position = UDim2.new(1, -82, 0.5, -12)
joinBtn.Size = UDim2.new(0, 74, 0, 24)
joinBtn.Font = Enum.Font.GothamBold
joinBtn.Text = "Join"
joinBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
joinBtn.TextSize = 12
Instance.new("UICorner", joinBtn).CornerRadius = UDim.new(0, 6)
joinBtn.MouseButton1Click:Connect(function()
pcall(function()
TeleportService:TeleportToPlaceInstance(info.PlaceId, info.JobId, LocalPlayer)
end)
end)
end
end
local function RefreshOnlinePlayers()
local response = safeRequest({
Url = FIREBASE_URL .. PRESENCE_PATH .. ".json",
Method = "GET"
})
ClearOnlineList()
if not response.Success or response.Body == "null" then
local empty = Instance.new("TextLabel", OnlineScroll)
empty.BackgroundTransparency = 1
empty.Size = UDim2.new(1, 0, 0, 24)
empty.Font = Enum.Font.Gotham
empty.Text = "No players online."
empty.TextColor3 = Color3.fromRGB(160, 160, 160)
empty.TextSize = 13
return
end
local ok, data = pcall(HttpService.JSONDecode, HttpService, response.Body)
if not ok then return end
local now = os.time()
for _, info in pairs(data) do
if (now - (info.LastSeen or 0)) <= PRESENCE_TTL then
BuildPlayerRow(info)
end
end
end
task.spawn(function()
while ScreenGui.Parent and task.wait(15) do
if OnlinePanel.Visible then RefreshOnlinePlayers() end
end
end)
-- 9. COMMAND SYSTEM
local flying = false
local flyBody = nil
local function StopFly()
flying = false
if flyBody then flyBody:Destroy() flyBody = nil end
local char = LocalPlayer.Character
if char then
local hum = char:FindFirstChildOfClass("Humanoid")
if hum then hum.PlatformStand = false end
end
end
local function StartFly()
local char = LocalPlayer.Character
if not char then return end
local hum = char:FindFirstChildOfClass("Humanoid")
local root = char:FindFirstChild("HumanoidRootPart")
if not hum or not root then return end
flying = true
hum.PlatformStand = true
local bg = Instance.new("BodyGyro", root)
bg.MaxTorque = Vector3.new(1e6, 1e6, 1e6)
bg.P = 1e6
flyBody = bg
local bv = Instance.new("BodyVelocity", root)
bv.MaxForce = Vector3.new(1e6, 1e6, 1e6)
bv.Velocity = Vector3.zero
local cam = workspace.CurrentCamera
local SPEED = 60
task.spawn(function()
while flying and root.Parent do
local cf = cam.CFrame
local dir = Vector3.zero
if UserInputService:IsKeyDown(Enum.KeyCode.W) then dir += cf.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.S) then dir -= cf.LookVector end
if UserInputService:IsKeyDown(Enum.KeyCode.A) then dir -= cf.RightVector end
if UserInputService:IsKeyDown(Enum.KeyCode.D) then dir += cf.RightVector end
if UserInputService:IsKeyDown(Enum.KeyCode.Space) then dir += Vector3.new(0,1,0) end
if UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then dir -= Vector3.new(0,1,0) end
bv.Velocity = dir.Magnitude > 0 and dir.Unit * SPEED or Vector3.zero
bg.CFrame = cf
task.wait()
end
bv:Destroy()
end)
end
local function PlayDance()
local char = LocalPlayer.Character
if not char then return end
local hum = char:FindFirstChildOfClass("Humanoid")
if not hum then return end
local animId = hum.RigType == Enum.HumanoidRigType.R15
and "rbxassetid://507771019"
or "rbxassetid://182435998"
local anim = Instance.new("Animation")
anim.AnimationId = animId
local track = hum:LoadAnimation(anim)
track:Play()
CreateMessage("System", "Dancing!", true)
end
local HELP_TEXT = {
"━━━ BetterChat Commands ━━━",
"/help — shows this list",
"/clear — clears the chat window",
"/e dance — makes your character dance",
"/console — opens the developer console",
"/fly — toggle flying (WASD + Space/Shift)",
"/speed [n] — sets your walk speed",
"/jump [n] — sets your jump power",
"/leave — exits the game",
"━━━━━━━━━━━━━━━━━━━━━━━━━━",
}
local function HandleCommand(input)
local args = {}
for word in input:gmatch("%S+") do table.insert(args, word) end
local cmd = args[1] and args[1]:lower()
if cmd == "/help" then
for _, line in ipairs(HELP_TEXT) do CreateMessage("System", line, true) end
elseif cmd == "/clear" then
for _, c in ipairs(ChatScroll:GetChildren()) do
if c:IsA("TextLabel") then c:Destroy() end
end
CreateMessage("System", "Chat cleared.", true)
elseif cmd == "/e" and args[2] and args[2]:lower() == "dance" then
PlayDance()
elseif cmd == "/console" then
StarterGui:SetCore("DevConsoleVisible", true)
elseif cmd == "/fly" then
if flying then
StopFly()
CreateMessage("System", "Fly disabled.", true)
else
StartFly()
CreateMessage("System", "Fly enabled. WASD to move, Space/Shift for up/down.", true)
end
elseif cmd == "/speed" then
local val = tonumber(args[2])
if not val then return true end
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
if hum then hum.WalkSpeed = val CreateMessage("System", "Walk speed set to " .. val, true) end
elseif cmd == "/jump" then
local val = tonumber(args[2])
if not val then return true end
local char = LocalPlayer.Character
local hum = char and char:FindFirstChildOfClass("Humanoid")
if hum then hum.JumpPower = val hum.JumpHeight = val CreateMessage("System", "Jump power set to " .. val, true) end
elseif cmd == "/leave" then
TeleportService:Teleport(game.PlaceId)
else
return false
end
return true
end
-- 10. FIRST RUN TUTORIAL
local function ShowTutorial()
local overlay = Instance.new("Frame", ScreenGui)
overlay.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
overlay.BackgroundTransparency = 0.4
overlay.Size = UDim2.new(1, 0, 1, 0)
overlay.ZIndex = 10
local card = Instance.new("Frame", overlay)
card.BackgroundColor3 = Color3.fromRGB(15, 15, 15)
card.BackgroundTransparency = 0.1
card.AnchorPoint = Vector2.new(0.5, 0.5)
card.Position = UDim2.new(0.5, 0, 0.5, 0)
card.Size = UDim2.new(0, 340, 0, 220)
card.ZIndex = 11
Instance.new("UICorner", card).CornerRadius = UDim.new(0, 12)
local titleLabel = Instance.new("TextLabel", card)
titleLabel.BackgroundTransparency = 1
titleLabel.Position = UDim2.new(0, 0, 0, 16)
titleLabel.Size = UDim2.new(1, 0, 0, 28)
titleLabel.Font = Enum.Font.GothamBold
titleLabel.TextColor3 = Color3.fromRGB(100, 200, 255)
titleLabel.TextSize = 18
titleLabel.TextXAlignment = Enum.TextXAlignment.Center
titleLabel.ZIndex = 12
local bodyLabel = Instance.new("TextLabel", card)
bodyLabel.BackgroundTransparency = 1
bodyLabel.Position = UDim2.new(0, 24, 0, 58)
bodyLabel.Size = UDim2.new(1, -48, 0, 110)
bodyLabel.Font = Enum.Font.GothamMedium
bodyLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
bodyLabel.TextSize = 14
bodyLabel.TextWrapped = true
bodyLabel.TextXAlignment = Enum.TextXAlignment.Center
bodyLabel.TextYAlignment = Enum.TextYAlignment.Top
bodyLabel.ZIndex = 12
local pageLabel = Instance.new("TextLabel", card)
pageLabel.BackgroundTransparency = 1
pageLabel.Position = UDim2.new(0, 16, 1, -36)
pageLabel.Size = UDim2.new(0.4, 0, 0, 30)
pageLabel.Font = Enum.Font.Gotham
pageLabel.TextColor3 = Color3.fromRGB(120, 120, 120)
pageLabel.TextSize = 12
pageLabel.TextXAlignment = Enum.TextXAlignment.Left
pageLabel.ZIndex = 12
local nextBtn = Instance.new("TextButton", card)
nextBtn.BackgroundColor3 = Color3.fromRGB(0, 150, 255)
nextBtn.AnchorPoint = Vector2.new(1, 1)
nextBtn.Position = UDim2.new(1, -16, 1, -16)
nextBtn.Size = UDim2.new(0, 100, 0, 30)
nextBtn.Font = Enum.Font.GothamBold
nextBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
nextBtn.TextSize = 14
nextBtn.ZIndex = 12
Instance.new("UICorner", nextBtn).CornerRadius = UDim.new(0, 8)
local pages = {
{
title = "👋 Welcome to BetterChat",
body = "BetterChat replaces Roblox's default chat with a global cross-server chat. You can talk to anyone running the script, no matter what server they're in."
},
{
title = "💬 How to Chat",
body = "Type your message in the box at the bottom of the chat window and press Enter to send.\n\nPress / anywhere in-game to quickly focus the chat box."
},
{
title = "👁 Toggling the Chat",
body = "Click the chat icon in the top-right to show or hide the chat window.\n\nA red badge will appear showing how many new messages you've missed while it's hidden."
},
{
title = "👥 Joining Other Players",
body = "Click the green 👥 button to open the Online Players panel.\n\nAnyone else running BetterChat appears here. Press Join to teleport directly into their server."
},
{
title = "⌨️ Commands",
body = "Type /help in chat to see all available commands.\n\nCommands include /fly, /speed, /jump, /clear, /leave, /e dance, and more."
},
{
title = "✅ You're all set!",
body = "BetterChat is now active. This tutorial won't show again.\n\nEnjoy chatting!"
}
}
local currentPage = 1
local function ShowPage(n)
local p = pages[n]
titleLabel.Text = p.title
bodyLabel.Text = p.body
pageLabel.Text = n .. " / " .. #pages
nextBtn.Text = n == #pages and "Done ✓" or "Next →"
end
ShowPage(1)
nextBtn.MouseButton1Click:Connect(function()
if currentPage < #pages then
currentPage += 1
ShowPage(currentPage)
else
overlay:Destroy()
end
end)
end
-- 11. CONNECTIONS
ToggleButton.MouseButton1Click:Connect(function()
Container.Visible = not Container.Visible
if Container.Visible then ClearUnread() end
end)
OnlineButton.MouseButton1Click:Connect(function()
OnlinePanel.Visible = not OnlinePanel.Visible
if OnlinePanel.Visible then RefreshOnlinePlayers() end
end)
TextBox.FocusLost:Connect(function(enter)
if enter and TextBox.Text ~= "" then
local input = TextBox.Text
TextBox.Text = ""
if not HandleCommand(input) then
local dName = LocalPlayer.DisplayName
local msgId = HttpService:GenerateGUID(false)
seenKeys[msgId] = true
-- إظهار الرسالة للمستخدم في اللحظة نفسها (Optimistic UI)
CreateMessage(dName, input, false)
ShowBubble(dName, input)
-- رفع الرسالة للسيرفر بالخلفية
task.spawn(function()
SendToFirebase(dName, input, msgId)
end)
end
end
end)
UserInputService.InputBegan:Connect(function(i, g)
if i.KeyCode == Enum.KeyCode.Slash and not g then
if not Container.Visible then Container.Visible = true end
task.wait()
TextBox:CaptureFocus()
end
end)
SeedAnnouncements()
StartPolling()
CreateMessage("System", "BetterChat V" .. CURRENT_VERSION_STRING .. " loaded. Type /help for commands.", true)
if isFirstRun then
ShowTutorial()
end
game:GetService("Players").PlayerAdded:Connect(function(player)
if typeof(AddMessage) == "function" then
AddMessage("System", player.Name .. " joined the chat", Color3.fromRGB(255, 255, 255))
end
end)