-- 💬 Enhanced Chat System - نظام شات محسن بالكامل (مُصلح) -- يحذف bubble chat الافتراضي ويستبدله بنظام مخصص متطور local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local RunService = game:GetService("RunService") local UserInputService = game:GetService("UserInputService") local TextChatService = game:GetService("TextChatService") local LocalPlayer = Players.LocalPlayer -- إعدادات محسنة local SETTINGS = { MaxDistance = 70, -- المسافة للرؤية الكاملة DotsDistance = 100, -- المسافة لرؤية النقاط DisplayTime = 8, -- وقت العرض الأساسي MaxBubbles = 3, -- أقصى عدد رسائل BubbleHeight = 35, -- ارتفاع الفقاعة BubbleSpacing = 15, -- المسافة بين الفقاعات (مُصلحة) MinWidth = 80, -- أقل عرض للفقاعة MaxWidth = 300, -- أقصى عرض للفقاعة TypingTimeout = 3 -- وقت انتظار قبل إزالة typing } -- متغيرات النظام local activeBubbles = {} -- تتبع الفقاعات النشطة local messageHistory = {} -- تاريخ الرسائل لمنع التكرار local typingStates = {} -- حالات الكتابة local playerConnections = {} -- اتصالات اللاعبين local storedMessages = {} -- الرسائل المحفوظة للعرض عند الاقتراب local lastTypingCheck = {} -- آخر فحص للكتابة local isLocalPlayerTyping = false -- حالة كتابة اللاعب المحلي -- تعطيل bubble chat الافتراضي فقط local function disableBubbleChat() pcall(function() -- تعطيل BubbleChat من Chat service local ChatService = game:GetService("Chat") ChatService.BubbleChatEnabled = false -- تعطيل TextChatService bubbles if TextChatService.BubbleChatConfiguration then TextChatService.BubbleChatConfiguration.Enabled = false end end) end -- حذف الفقاعات الافتراضية من رؤوس اللاعبين (مُصلح أكثر) local function removeDefaultBubbles() for _, player in pairs(Players:GetPlayers()) do if player.Character and player.Character:FindFirstChild("Head") then local head = player.Character.Head for _, child in pairs(head:GetChildren()) do if child:IsA("BillboardGui") then -- فقط حذف الفقاعات التي تحتوي على نص شات local isDefaultChatBubble = false -- تجاهل الـ BillboardGuis المخصصة تماماً if child.Name:find("CustomChat") or child.Name:find("TypingIndicator") or child.Name:find("Overhead") or child.Name:find("HealthBar") or child.Name:find("NameTag") or child.Name:find("Health") or child.Name:find("Name") or child.Name:find("Tag") then continue -- تخطى هذا العنصر end -- فحص أكثر تفصيلاً للفقاعات الافتراضية local textLabel = child:FindFirstChildOfClass("TextLabel") if textLabel and textLabel.Text and textLabel.Text:len() > 0 then -- فحص إذا كان النص يبدو مثل رسالة شات local text = textLabel.Text if text ~= "" and not text:find("Health") and not text:find("HP") and not text:find("%d+/%d+") then -- تأكد أنه ليس رقم أو معلومات صحة local hasFrame = child:FindFirstChildOfClass("Frame") if hasFrame and hasFrame.BackgroundTransparency < 1 then isDefaultChatBubble = true end end end -- حذف فقط إذا كان فقاعة شات افتراضية مؤكدة if isDefaultChatBubble then child:Destroy() end end end end end end -- حساب المسافة local function getPlayerDistance(player) if not LocalPlayer.Character or not LocalPlayer.Character.PrimaryPart then return math.huge end if not player.Character or not player.Character.PrimaryPart then return math.huge end return (LocalPlayer.Character.PrimaryPart.Position - player.Character.PrimaryPart.Position).Magnitude end -- حساب وقت العرض المناسب local function calculateDisplayTime(message) local baseTime = SETTINGS.DisplayTime local lengthMultiplier = math.min(#message / 15, 4) return baseTime + lengthMultiplier end -- حساب عرض الفقاعة local function calculateBubbleWidth(message) local textLength = #message local width = math.max(SETTINGS.MinWidth, math.min(textLength * 8 + 40, SETTINGS.MaxWidth)) return width end -- فحص التكرار المحسن (حماية قوية جداً من التكرار) local function isDuplicateMessage(player, message) local playerId = player.UserId if not messageHistory[playerId] then messageHistory[playerId] = {} end local history = messageHistory[playerId] local currentTime = tick() -- تنظيف التاريخ القديم (أكثر من 15 ثانية) for i = #history, 1, -1 do if currentTime - history[i].time > 15 then table.remove(history, i) end end -- فحص التكرار (حماية قوية) local timeThreshold = 5 -- 5 ثوانِ لمنع التكرار تماماً for _, record in pairs(history) do if record.message == message and currentTime - record.time < timeThreshold then return true -- رسالة مكررة end end -- إضافة الرسالة الجديدة للتاريخ table.insert(history, {message = message, time = currentTime}) -- الحد من حجم التاريخ if #history > 10 then table.remove(history, 1) end return false end -- إنشاء مؤشر الكتابة المحسن local function createTypingIndicator(player) if not player.Character or not player.Character:FindFirstChild("Head") then return nil end local head = player.Character.Head local distance = getPlayerDistance(player) if distance > SETTINGS.DotsDistance then return nil end -- إزالة أي مؤشر كتابة موجود local existing = head:FindFirstChild("TypingIndicator") if existing then existing:Destroy() end -- إنشاء BillboardGui local billboard = Instance.new("BillboardGui") billboard.Name = "TypingIndicator" billboard.Size = UDim2.new(0, 0, 0, 0) billboard.StudsOffset = Vector3.new(0, 2.5, 0) billboard.LightInfluence = 0 billboard.Parent = head -- الإطار الرئيسي local frame = Instance.new("Frame") frame.Size = UDim2.new(1, 0, 1, 0) frame.BackgroundColor3 = Color3.fromRGB(45, 45, 55) frame.BackgroundTransparency = 0.15 frame.BorderSizePixel = 0 frame.Parent = billboard -- الحواف المستديرة local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 12) corner.Parent = frame -- تأثير التوهج local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(80, 80, 90) stroke.Thickness = 1 stroke.Transparency = 0.3 stroke.Parent = frame -- إنشاء النقاط الثلاث local dotsContainer = Instance.new("Frame") dotsContainer.Size = UDim2.new(1, 0, 1, 0) dotsContainer.BackgroundTransparency = 1 dotsContainer.Parent = frame local dots = {} for i = 1, 3 do local dot = Instance.new("Frame") dot.Name = "Dot" .. i dot.Size = UDim2.new(0, 6, 0, 6) dot.Position = UDim2.new(0, 15 + (i-1) * 12, 0.5, -3) dot.BackgroundColor3 = Color3.fromRGB(180, 180, 200) dot.BorderSizePixel = 0 dot.Parent = dotsContainer local dotCorner = Instance.new("UICorner") dotCorner.CornerRadius = UDim.new(1, 0) dotCorner.Parent = dot table.insert(dots, dot) end -- انيميشن الظهور local appearTween = TweenService:Create(billboard, TweenInfo.new(0.2, Enum.EasingStyle.Back, Enum.EasingDirection.Out), {Size = UDim2.new(0, 70, 0, 25)}) appearTween:Play() -- انيميشن النقاط المتدفق والأسلس spawn(function() local animationRunning = true while billboard.Parent and animationRunning do for i, dot in pairs(dots) do if not billboard.Parent then break end -- انيميشن صعود ونزول أسلس local upTween = TweenService:Create(dot, TweenInfo.new(0.4, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), { Position = UDim2.new(0, 15 + (i-1) * 12, 0.3, -3), BackgroundTransparency = 0 }) upTween:Play() spawn(function() wait(0.2) if billboard.Parent then local downTween = TweenService:Create(dot, TweenInfo.new(0.4, Enum.EasingStyle.Sine, Enum.EasingDirection.In), { Position = UDim2.new(0, 15 + (i-1) * 12, 0.5, -3), BackgroundTransparency = 0.3 }) downTween:Play() end end) wait(0.12) -- تأخير قصير بين النقاط end wait(0.5) -- انتظار قبل إعادة التكرار end end) return billboard end -- إزالة مؤشر الكتابة local function removeTypingIndicator(player) if not player.Character or not player.Character:FindFirstChild("Head") then return end local head = player.Character.Head local indicator = head:FindFirstChild("TypingIndicator") if indicator then local hideTween = TweenService:Create(indicator, TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {Size = UDim2.new(0, 0, 0, 0)}) hideTween:Play() hideTween.Completed:Connect(function() indicator:Destroy() end) end end -- إنشاء فقاعة الشات المحسنة local function createChatBubble(player, message, isDotsOnly) if not player.Character or not player.Character:FindFirstChild("Head") then return nil end local head = player.Character.Head local bubbleId = "CustomChat_" .. tick() -- إنشاء BillboardGui local billboard = Instance.new("BillboardGui") billboard.Name = bubbleId billboard.Size = UDim2.new(0, 0, 0, 0) billboard.StudsOffset = Vector3.new(0, 2.2, 0) billboard.LightInfluence = 0 billboard.Parent = head -- الإطار الرئيسي مع تدرج لوني local frame = Instance.new("Frame") frame.Size = UDim2.new(1, 0, 1, 0) frame.BackgroundColor3 = isDotsOnly and Color3.fromRGB(60, 60, 75) or Color3.fromRGB(35, 35, 45) frame.BackgroundTransparency = 0.05 frame.BorderSizePixel = 0 frame.Parent = billboard -- حواف مستديرة أكثر local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 12) corner.Parent = frame -- تأثير حدود متوهجة local stroke = Instance.new("UIStroke") stroke.Color = isDotsOnly and Color3.fromRGB(100, 100, 120) or Color3.fromRGB(70, 70, 90) stroke.Thickness = 1.5 stroke.Transparency = 0.4 stroke.Parent = frame -- النص local textLabel = Instance.new("TextLabel") textLabel.Size = UDim2.new(1, -16, 1, -6) textLabel.Position = UDim2.new(0, 8, 0, 3) textLabel.BackgroundTransparency = 1 textLabel.TextColor3 = isDotsOnly and Color3.fromRGB(160, 160, 180) or Color3.fromRGB(255, 255, 255) textLabel.Font = Enum.Font.GothamMedium textLabel.TextScaled = true textLabel.Text = isDotsOnly and "●●●" or message textLabel.TextWrapped = true textLabel.TextXAlignment = Enum.TextXAlignment.Center textLabel.TextYAlignment = Enum.TextYAlignment.Center textLabel.Parent = frame -- قيود حجم النص local textConstraint = Instance.new("UITextSizeConstraint") textConstraint.MaxTextSize = 18 textConstraint.MinTextSize = 12 textConstraint.Parent = textLabel -- تأثير الظل local shadow = Instance.new("Frame") shadow.Size = UDim2.new(1, 4, 1, 4) shadow.Position = UDim2.new(0, -2, 0, 2) shadow.BackgroundColor3 = Color3.fromRGB(0, 0, 0) shadow.BackgroundTransparency = 0.8 shadow.BorderSizePixel = 0 shadow.ZIndex = frame.ZIndex - 1 shadow.Parent = billboard local shadowCorner = Instance.new("UICorner") shadowCorner.CornerRadius = UDim.new(0, 12) shadowCorner.Parent = shadow return billboard end -- تنظيم الفقاعات بشكل أفضل local function organizeBubbles(player) if not player.Character or not player.Character:FindFirstChild("Head") then return end local head = player.Character.Head local bubbles = {} -- جمع الفقاعات المخصصة فقط for _, child in pairs(head:GetChildren()) do if child.Name:find("CustomChat_") then local timestamp = tonumber(child.Name:match("CustomChat_(.+)")) if timestamp then table.insert(bubbles, { gui = child, time = timestamp }) end end end -- ترتيب حسب الوقت (الأحدث في الأعلى) table.sort(bubbles, function(a, b) return a.time > b.time end) -- حذف الفقاعات الزائدة while #bubbles > SETTINGS.MaxBubbles do local oldBubble = table.remove(bubbles) if oldBubble.gui then oldBubble.gui:Destroy() end end -- ترتيب المواقع (الأحدث في الأعلى) - مُصلح for i, bubble in pairs(bubbles) do local yOffset = (i - 1) * (SETTINGS.BubbleSpacing / 10) bubble.gui.StudsOffset = Vector3.new(0, 2.2 + yOffset, 0) end -- حفظ الفقاعات النشطة activeBubbles[player.UserId] = bubbles end -- عرض الرسالة مع نظام متقدم (محسن لمنع التكرار) local function displayMessage(player, message, forceShow) -- فحص التكرار أولاً (حتى للاعب المحلي) if isDuplicateMessage(player, message) then return end -- للاعب المحلي، اعرض الرسالة دائماً (بعد فحص التكرار) if player == LocalPlayer then forceShow = true end -- فحص المسافة local distance = getPlayerDistance(player) local showDots = distance > SETTINGS.MaxDistance and distance <= SETTINGS.DotsDistance local showFull = distance <= SETTINGS.MaxDistance or forceShow if not showFull and not showDots then -- حفظ الرسالة للعرض عند الاقتراب storedMessages[player.UserId] = { message = message, time = tick(), displayTime = calculateDisplayTime(message) } return end -- إزالة مؤشر الكتابة removeTypingIndicator(player) if typingStates[player.UserId] then typingStates[player.UserId] = nil end -- إنشاء الفقاعة local bubble = createChatBubble(player, message, showDots and not forceShow) if not bubble then return end -- تحديد الأبعاد local width = showDots and 70 or calculateBubbleWidth(message) local height = SETTINGS.BubbleHeight -- انيميشن ظهور سريع وأنيق local showTween = TweenService:Create(bubble, TweenInfo.new(0.12, Enum.EasingStyle.Quart, Enum.EasingDirection.Out), {Size = UDim2.new(0, width, 0, height)}) showTween:Play() -- تنظيم الفقاعات فوراً showTween.Completed:Connect(function() organizeBubbles(player) end) -- برمجة الاختفاء local displayTime = showDots and 4 or calculateDisplayTime(message) spawn(function() wait(displayTime) if bubble and bubble.Parent then local hideTween = TweenService:Create(bubble, TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.In), {Size = UDim2.new(0, 0, 0, 0)}) hideTween:Play() hideTween.Completed:Connect(function() if bubble then bubble:Destroy() organizeBubbles(player) end end) end end) end -- نظام كشف الكتابة المحسن والمتقدم local function setupAdvancedTypingDetection(player) if player == LocalPlayer then -- كشف كتابة اللاعب المحلي المحسن spawn(function() wait(1) -- متغيرات للتتبع local lastFocusState = false local lastTextLength = 0 local typingStartTime = 0 while player.Parent do local isCurrentlyTyping = false local currentTime = tick() -- فحص التركيز على صندوق النص local focusedTextBox = UserInputService:GetFocusedTextBox() if focusedTextBox then local parent = focusedTextBox.Parent -- فحص إذا كان صندوق النص جزء من الشات while parent do if parent.Name == "Chat" or parent.Name == "ChatFrame" or parent.Name == "Frame" or parent.Name == "ChatBar" or parent.Name == "TextBox" then isCurrentlyTyping = true break end parent = parent.Parent -- تجنب حلقة لا نهائية if not parent or parent == game then break end end -- فحص إضافي بناءً على خصائص TextBox if not isCurrentlyTyping and focusedTextBox then local textLength = #focusedTextBox.Text -- إذا تغير طول النص، فربما يكتب في الشات if textLength ~= lastTextLength then lastTextLength = textLength isCurrentlyTyping = true end end end -- فحص إضافي للكتابة بناءً على الإدخال if not isCurrentlyTyping then -- فحص إذا ضغط مفاتيح نص مؤخراً local keyPressed = false for i = 32, 126 do -- ASCII للأحرف والأرقام if UserInputService:IsKeyDown(Enum.KeyCode(i)) then keyPressed = true break end end if keyPressed and focusedTextBox then isCurrentlyTyping = true end end -- تطبيق حالة الكتابة if isCurrentlyTyping and not typingStates[player.UserId] then typingStates[player.UserId] = {active = true, startTime = currentTime} createTypingIndicator(player) typingStartTime = currentTime isLocalPlayerTyping = true elseif not isCurrentlyTyping and typingStates[player.UserId] then -- تأخير قصير قبل إزالة المؤشر لتجنب الوميض spawn(function() wait(0.5) if not isLocalPlayerTyping then removeTypingIndicator(player) typingStates[player.UserId] = nil end end) isLocalPlayerTyping = false end lastFocusState = isCurrentlyTyping wait(0.1) -- فحص سريع جداً end end) else -- كشف كتابة اللاعبين الآخرين المحسن spawn(function() wait(math.random(1, 3)) -- تأخير عشوائي للتنويع while player.Parent do local distance = getPlayerDistance(player) -- زيادة احتمالية إظهار الكتابة للاعبين القريبين local typingChance = 2 if distance <= SETTINGS.MaxDistance then typingChance = 5 elseif distance <= SETTINGS.DotsDistance then typingChance = 3 end if math.random(1, 100) <= typingChance then if distance <= SETTINGS.DotsDistance and not typingStates[player.UserId] then typingStates[player.UserId] = {active = true, startTime = tick()} createTypingIndicator(player) -- إزالة بعد وقت عشوائي spawn(function() wait(math.random(2, 5)) if typingStates[player.UserId] then removeTypingIndicator(player) typingStates[player.UserId] = nil end end) end end wait(math.random(1, 2)) end end) end end -- إعداد اللاعب الشامل local function setupPlayer(player) local playerId = player.UserId -- تنظيف الاتصالات القديمة if playerConnections[playerId] then for _, connection in pairs(playerConnections[playerId]) do if connection then connection:Disconnect() end end end playerConnections[playerId] = {} -- ربط حدث الرسالة local chatConnection = player.Chatted:Connect(function(message) displayMessage(player, message) end) table.insert(playerConnections[playerId], chatConnection) -- ربط حدث إضافة الشخصية local characterConnection = player.CharacterAdded:Connect(function() wait(1) -- انتظار أطول للتأكد من تحميل الشخصية removeDefaultBubbles() setupAdvancedTypingDetection(player) end) table.insert(playerConnections[playerId], characterConnection) -- إعداد كشف الكتابة للشخصية الحالية if player.Character then setupAdvancedTypingDetection(player) end end -- تطبيق على جميع اللاعبين Players.PlayerAdded:Connect(setupPlayer) for _, player in pairs(Players:GetPlayers()) do setupPlayer(player) end -- تنظيف عند خروج اللاعب Players.PlayerRemoving:Connect(function(player) local playerId = player.UserId -- تنظيف البيانات messageHistory[playerId] = nil activeBubbles[playerId] = nil typingStates[playerId] = nil storedMessages[playerId] = nil lastTypingCheck[playerId] = nil -- قطع الاتصالات if playerConnections[playerId] then for _, connection in pairs(playerConnections[playerId]) do if connection then connection:Disconnect() end end playerConnections[playerId] = nil end end) -- تعطيل bubble chat الافتراضي فوراً disableBubbleChat() -- إعداد إضافي محذوف لمنع التكرار -- تم حذف TextChatService.MessageReceived لمنع التكرار -- حلقة التنظيف والإدارة الرئيسية المحسنة (مُصلحة) spawn(function() while wait(0.5) do -- فحص أبطأ لتقليل استهلاك الموارد -- حذف الفقاعات الافتراضية باستمرار (مرة واحدة كل نصف ثانية) removeDefaultBubbles() -- إدارة الرسائل المحفوظة والمسافات for _, player in pairs(Players:GetPlayers()) do local distance = getPlayerDistance(player) local playerId = player.UserId -- إذا كان بعيد جداً، نظف كل شيء (إلا اللاعب المحلي) if distance > SETTINGS.DotsDistance and player ~= LocalPlayer then if player.Character and player.Character:FindFirstChild("Head") then local head = player.Character.Head for _, child in pairs(head:GetChildren()) do if child.Name:find("CustomChat_") or child.Name == "TypingIndicator" then child:Destroy() end end end -- نظف البيانات if storedMessages[playerId] and tick() - storedMessages[playerId].time > 15 then storedMessages[playerId] = nil end if typingStates[playerId] then typingStates[playerId] = nil end -- إذا اقترب ولديه رسالة محفوظة، اعرضها elseif distance <= SETTINGS.MaxDistance and storedMessages[playerId] then local stored = storedMessages[playerId] if tick() - stored.time < stored.displayTime then displayMessage(player, stored.message, true) end storedMessages[playerId] = nil end -- تنظيف مؤشرات الكتابة القديمة if typingStates[playerId] and tick() - typingStates[playerId].startTime > SETTINGS.TypingTimeout then removeTypingIndicator(player) typingStates[playerId] = nil if player == LocalPlayer then isLocalPlayerTyping = false end end end end end) -- مراقبة مستمرة لتعطيل bubble chat الافتراضي (مُبطأة) spawn(function() while wait(5) do -- كل 5 ثواني بدلاً من 2 لتوفير الأداء أكثر disableBubbleChat() end end) -- مراقبة خاصة للاعب المحلي لضمان ظهور رسائله spawn(function() while wait(0.05) do -- فحص سريع جداً للاعب المحلي if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("Head") then -- التأكد من أن مؤشر الكتابة يعمل بشكل صحيح للاعب المحلي local focusedTextBox = UserInputService:GetFocusedTextBox() if focusedTextBox then local isChatBox = false local parent = focusedTextBox.Parent while parent and parent ~= game do if parent.Name == "Chat" or parent.Name == "ChatFrame" or parent.Name == "Frame" or parent.Name == "ChatBar" then isChatBox = true break end parent = parent.Parent end if isChatBox and not typingStates[LocalPlayer.UserId] then isLocalPlayerTyping = true typingStates[LocalPlayer.UserId] = {active = true, startTime = tick()} createTypingIndicator(LocalPlayer) elseif not isChatBox and typingStates[LocalPlayer.UserId] and isLocalPlayerTyping then isLocalPlayerTyping = false spawn(function() wait(0.3) if not isLocalPlayerTyping and typingStates[LocalPlayer.UserId] then removeTypingIndicator(LocalPlayer) typingStates[LocalPlayer.UserId] = nil end end) end elseif typingStates[LocalPlayer.UserId] and isLocalPlayerTyping then isLocalPlayerTyping = false spawn(function() wait(0.3) if not isLocalPlayerTyping and typingStates[LocalPlayer.UserId] then removeTypingIndicator(LocalPlayer) typingStates[LocalPlayer.UserId] = nil end end) end end end end) -- backup للتأكد من عرض رسائل اللاعب المحلي (محذوف لمنع التكرار) -- تم حذف هذا الجزء لمنع التكرار حيث أن setupPlayer يتعامل مع هذا بالفعل