· 6 years ago · Nov 20, 2019, 03:28 AM
1-[[
2 // FileName: Chat.lua
3 // Written by: SolarCrane
4 // Description: Code for lua side chat on ROBLOX.
5]]
6
7--[[ CONSTANTS ]]
8
9-- NOTE: IF YOU WANT TO USE THIS CHAT SCRIPT IN YOUR OWN GAME:
10-- 1) COPY THE CONTENTS OF THIS FILE INTO A MODULE
11-- 2) CREATE A LOCALSCRIPT AND PARENT IT TO StarterGui
12-- 3) IN THE LOCALSCRIPT require() THE CHAT MODULE YOU MADE IN STEP 1
13-- 4) CONFIGURE YOUR PLACE ON THE WEBSITE TO USE BUBBLE-CHAT
14-- 5) SET THE FOLLOWING TWO VARIABLES TO TRUE
15local FORCE_CHAT_GUI = false
16local NON_CORESCRIPT_MODE = false
17-- 6) (OPTIONAL) PUT THE FOLLOWING LINE IN A SERVER SCRIPT TO MAKE CHAT PERSIST THROUGH RESPAWNING
18-- game:GetService('StarterGui').ResetPlayerGuiOnSpawn = false
19---------------------------------
20
21local MESSAGES_FADE_OUT_TIME = 30
22local MAX_UDIM_SIZE = 2^15 - 1
23
24local CHAT_WINDOW_Y_OFFSET = 2
25
26local PHONE_SCREEN_WIDTH = 640
27local TABLET_SCREEN_WIDTH = 1024
28
29local FLOOD_CHECK_MESSAGE_COUNT = 7
30local FLOOD_CHECK_MESSAGE_INTERVAL = 15 -- This is in seconds
31
32local VR_CHAT_CLICK_DEBOUNCE = 0.25
33
34local SCROLLBAR_THICKNESS = 7
35local CHAT_COLORS =
36{
37 Color3.new(253/255, 41/255, 67/255), -- BrickColor.new("Bright red").Color,
38 Color3.new(1/255, 162/255, 255/255), -- BrickColor.new("Bright blue").Color,
39 Color3.new(2/255, 184/255, 87/255), -- BrickColor.new("Earth green").Color,
40 BrickColor.new("Bright violet").Color,
41 BrickColor.new("Bright orange").Color,
42 BrickColor.new("Bright yellow").Color,
43 BrickColor.new("Light reddish violet").Color,
44 BrickColor.new("Brick yellow").Color,
45}
46
47local thisModuleName = "Chat"
48
49local emptySelectionImage = Instance.new("ImageLabel")
50emptySelectionImage.ImageTransparency = 1
51emptySelectionImage.BackgroundTransparency = 1
52
53--[[ END OF CONSTANTS ]]
54
55--[[ SERVICES ]]
56local RunService = game:GetService('RunService')
57local CoreGuiService = game:GetService('CoreGui')
58local PlayersService = game:GetService('Players')
59local DebrisService = game:GetService('Debris')
60local GuiService = game:GetService('GuiService')
61local InputService = game:GetService('UserInputService')
62local StarterGui = game:GetService('StarterGui')
63local ContextActionService = game:GetService('ContextActionService')
64local Settings = UserSettings()
65local GameSettings = Settings.GameSettings
66--[[ END OF SERVICES ]]
67
68--[[ Fast Flags ]]--
69local FFlagEnableNewDevConsole = settings():GetFFlag("EnableNewDevConsole")
70
71--[[ SCRIPT VARIABLES ]]
72local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
73local VRHub = require(RobloxGui.Modules.VR.VRHub)
74local PlayerPermissionsModule = require(RobloxGui.Modules.PlayerPermissionsModule)
75local StatsUtils = require(RobloxGui.Modules.Stats.StatsUtils)
76
77-- I am not fond of waiting at the top of the script here...
78while PlayersService.LocalPlayer == nil do PlayersService.ChildAdded:wait() end
79local Player = PlayersService.LocalPlayer
80-- GuiRoot will act as the top-node for parenting GUIs
81local GuiRoot = Instance.new('Frame')
82GuiRoot.Name = 'GuiRoot';
83GuiRoot.Size = UDim2.new(1,0,1,0);
84GuiRoot.BackgroundTransparency = 1;
85
86
87
88local chatRepositioned = false
89local chatBarDisabled = false
90
91local lastSelectedPlayer = nil
92local lastSelectedButton = nil
93
94local blockingUtility = nil
95
96local topbarEnabled = true
97
98if not NON_CORESCRIPT_MODE and not InputService.VREnabled then
99 blockingUtility = require(RobloxGui.Modules:WaitForChild("PlayerDropDown")):CreateBlockingUtility()
100end
101
102--[[ END OF SCRIPT VARIABLES ]]
103
104local function GetLuaChatFilteringFlag()
105 return true
106end
107
108local Util = {}
109do
110 -- Check if we are running on a touch device
111 function Util.IsTouchDevice()
112 local touchEnabled = false
113 pcall(function() touchEnabled = InputService.TouchEnabled end)
114 return touchEnabled
115 end
116
117 function Util.IsSmallScreenSize()
118 return GuiRoot.AbsoluteSize.X <= PHONE_SCREEN_WIDTH
119 end
120
121 function Util.Create(instanceType)
122 return function(data)
123 local obj = Instance.new(instanceType)
124 for k, v in pairs(data) do
125 if type(k) == 'number' then
126 v.Parent = obj
127 else
128 obj[k] = v
129 end
130 end
131 return obj
132 end
133 end
134
135 function Util.Clamp(low, high, input)
136 return math.max(low, math.min(high, input))
137 end
138
139 function Util.Linear(t, b, c, d)
140 if t >= d then return b + c end
141
142 return c*t/d + b
143 end
144
145 function Util.EaseOutQuad(t, b, c, d)
146 if t >= d then return b + c end
147
148 t = t/d;
149 return -c * t*(t-2) + b
150 end
151
152 function Util.EaseInOutQuad(t, b, c, d)
153 if t >= d then
154 return b + c
155 end
156
157 t = t / (d/2);
158 if (t < 1) then
159 return c/2*t*t + b
160 end;
161 t = t - 1;
162 return -c/2 * (t*(t-2) - 1) + b;
163 end
164
165 function Util.PropertyTweener(instance, prop, start, final, duration, easingFunc, cbFunc)
166 local this = {}
167 this.StartTime = tick()
168 this.EndTime = this.StartTime + duration
169 this.Cancelled = false
170
171 local finished = false
172 local percentComplete = 0
173 spawn(function()
174 local now = tick()
175 while now < this.EndTime and instance do
176 if this.Cancelled then
177 return
178 end
179 instance[prop] = easingFunc(now - this.StartTime, start, final - start, duration)
180 percentComplete = Util.Clamp(0, 1, (now - this.StartTime) / duration)
181 RunService.RenderStepped:wait()
182 now = tick()
183 end
184 if this.Cancelled == false and instance then
185 instance[prop] = final
186 finished = true
187 percentComplete = 1
188 if cbFunc then
189 cbFunc()
190 end
191 end
192 end)
193
194 function this:GetPercentComplete()
195 return percentComplete
196 end
197
198 function this:IsFinished()
199 return finished
200 end
201
202 function this:Cancel()
203 this.Cancelled = true
204 end
205
206 return this
207 end
208
209 function Util.Signal()
210 local sig = {}
211
212 local mSignaler = Instance.new('BindableEvent')
213
214 local mArgData = nil
215 local mArgDataCount = nil
216
217 function sig:fire(...)
218 mArgData = {...}
219 mArgDataCount = select('#', ...)
220 mSignaler:Fire()
221 end
222
223 function sig:connect(f)
224 if not f then error("connect(nil)", 2) end
225 return mSignaler.Event:connect(function()
226 f(unpack(mArgData, 1, mArgDataCount))
227 end)
228 end
229
230 function sig:wait()
231 mSignaler.Event:wait()
232 assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
233 return unpack(mArgData, 1, mArgDataCount)
234 end
235
236 return sig
237 end
238
239 function Util.DisconnectEvent(conn)
240 if conn then
241 conn:disconnect()
242 end
243 return nil
244 end
245
246 function Util.SetGUIInsetBounds(x, y)
247 local success, _ = pcall(function() GuiService:SetGlobalGuiInset(0, x, 0, y) end)
248 if not success then
249 pcall(function() GuiService:SetGlobalSizeOffsetPixel(-x, -y) end) -- Legacy GUI-offset function
250 end
251 end
252
253 local baseUrl = game:GetService("ContentProvider").BaseUrl:lower()
254 baseUrl = string.gsub(baseUrl,"/m.","/www.") --mobile site does not work for this stuff!
255 function Util.GetSecureApiBaseUrl()
256 local secureApiUrl = baseUrl
257 secureApiUrl = string.gsub(secureApiUrl,"http://","https://")
258 secureApiUrl = string.gsub(secureApiUrl,"www","api")
259 return secureApiUrl
260 end
261
262 function Util.GetPlayerByName(playerName)
263 -- O(n), may be faster if I store a reverse hash from the players list; can't trust FindFirstChild in PlayersService because anything can be parented to there.
264 local lowerName = string.lower(playerName)
265 for _, player in pairs(PlayersService:GetPlayers()) do
266 if string.lower(player.Name) == lowerName then
267 return player
268 end
269 end
270 return nil -- Found no player
271 end
272
273 local function MakeIsInGroup(groupId, requiredRank)
274 assert(type(requiredRank) == "nil" or type(requiredRank) == "number", "requiredRank must be a number or nil")
275
276 local inGroupCache = {}
277 return function(player)
278 if player and player.UserId then
279 local userId = player.UserId
280
281 if inGroupCache[userId] == nil then
282 local inGroup = false
283 pcall(function() -- Many things can error is the IsInGroup check
284 if requiredRank then
285 inGroup = player:GetRankInGroup(groupId) > requiredRank
286 else
287 inGroup = player:IsInGroup(groupId)
288 end
289 end)
290 inGroupCache[userId] = inGroup
291 end
292
293 return inGroupCache[userId]
294 end
295
296 return false
297 end
298 end
299 Util.IsPlayerAdminAsync = MakeIsInGroup(1200769)
300 Util.IsPlayerInternAsync = MakeIsInGroup(2868472, 100)
301
302 local function GetNameValue(pName)
303 local value = 0
304 for index = 1, #pName do
305 local cValue = string.byte(string.sub(pName, index, index))
306 local reverseIndex = #pName - index + 1
307 if #pName%2 == 1 then
308 reverseIndex = reverseIndex - 1
309 end
310 if reverseIndex%4 >= 2 then
311 cValue = -cValue
312 end
313 value = value + cValue
314 end
315 return value
316 end
317
318 function Util.ComputeChatColor(pName)
319 return CHAT_COLORS[(GetNameValue(pName) % #CHAT_COLORS) + 1]
320 end
321
322 -- This is a memo-izing function
323 local testLabel = Instance.new('TextLabel')
324 testLabel.TextWrapped = true;
325 testLabel.Position = UDim2.new(1,0,1,0)
326 testLabel.Parent = GuiRoot -- Note: We have to parent it to check TextBounds
327 -- The TextSizeCache table looks like this Text->Font->sizeBounds->FontSize
328 local TextSizeCache = {}
329 function Util.GetStringTextBounds(text, font, fontSize, sizeBounds)
330 -- If no sizeBounds are specified use some huge number
331 sizeBounds = sizeBounds or false
332 if not TextSizeCache[text] then
333 TextSizeCache[text] = {}
334 end
335 if not TextSizeCache[text][font] then
336 TextSizeCache[text][font] = {}
337 end
338 if not TextSizeCache[text][font][sizeBounds] then
339 TextSizeCache[text][font][sizeBounds] = {}
340 end
341 if not TextSizeCache[text][font][sizeBounds][fontSize] then
342 testLabel.Text = text
343 testLabel.Font = font
344 testLabel.FontSize = fontSize
345 if sizeBounds then
346 testLabel.TextWrapped = true;
347 testLabel.Size = sizeBounds
348 else
349 testLabel.TextWrapped = false;
350 end
351 TextSizeCache[text][font][sizeBounds][fontSize] = testLabel.TextBounds
352 end
353 return TextSizeCache[text][font][sizeBounds][fontSize]
354 end
355
356 local PRINTABLE_CHARS = '[^' .. string.char(32) .. '-' .. string.char(126) .. ']'
357 local WHITESPACE_CHARS = '(' .. string.rep('%s', 7) .. ')%s+'
358 function Util.FilterUnprintableCharacters(str)
359 if not GetLuaChatFilteringFlag() then
360 return str
361 end
362
363 local result = str:gsub(PRINTABLE_CHARS, '');
364 result = str:gsub(WHITESPACE_CHARS, '%1');
365 return result
366 end
367end
368
369local SelectChatModeEvent = Util.Signal()
370local SelectPlayerEvent = Util.Signal()
371
372local function CreateChatMessage()
373 local this = {}
374 this.FadeRoutines = {}
375
376 function this:GetMessageFontSize(settings)
377 return Util.IsSmallScreenSize() and settings.SmallScreenFontSize or settings.FontSize
378 end
379
380 function this:OnResize()
381 -- Nothing!
382 end
383
384 function this:FadeIn()
385 local gui = this:GetGui()
386 if gui then
387 gui.Visible = true
388 end
389 end
390
391 function this:FadeOut()
392 local gui = this:GetGui()
393 if gui then
394 gui.Visible = false
395 end
396 end
397
398 function this:GetGui()
399 return this.Container
400 end
401
402 function this:Destroy()
403 if this.Container ~= nil then
404 this.Container:Destroy()
405 this.Container = nil
406 end
407 if this.FadeRoutines then
408 for _, routine in pairs(this.FadeRoutines) do
409 routine:Cancel()
410 end
411 this.FadeRoutines = {}
412 end
413 end
414
415 return this
416end
417
418local function CreateSystemChatMessage(settings, chattedMessage)
419 local this = CreateChatMessage()
420
421 this.Settings = settings
422 this.rawChatString = chattedMessage
423
424 function this:OnResize(containerSize)
425 if this.Container and this.ChatMessage then
426
427 if InputService.VREnabled then
428 this.ChatMessage.Position = UDim2.new(0, 4, 0, 0)
429 this.ChatMessage.Size = UDim2.new(1, 0, 1, 0)
430 end
431
432 this.Container.Size = UDim2.new(1,0,0,1000)
433 local textHeight = this.ChatMessage.TextBounds.Y
434
435 local newContainerHeight = textHeight + 5
436 this.Container.Size = UDim2.new(1,0,0,newContainerHeight)
437 return newContainerHeight
438 end
439 end
440
441 function this:FadeIn()
442 local gui = this:GetGui()
443 if gui then
444 gui.Visible = true
445 for _, routine in pairs(this.FadeRoutines) do
446 routine:Cancel()
447 end
448 this.FadeRoutines = {}
449 local tweenableObjects = {
450 this.ChatMessage;
451 }
452 for _, object in pairs(tweenableObjects) do
453 object.TextTransparency = 0;
454 object.TextStrokeTransparency = this.Settings.TextStrokeTransparency;
455 end
456
457 if this.MessageBackgroundImage then
458 this.MessageBackgroundImage.Visible = InputService.VREnabled
459 end
460 end
461 end
462
463 function this:FadeOut(instant)
464 local gui = this:GetGui()
465 if gui then
466 if instant then
467 gui.Visible = false
468 else
469 local tweenableObjects = {
470 this.ChatMessage;
471 }
472 for _, object in pairs(tweenableObjects) do
473 table.insert(this.FadeRoutines, Util.PropertyTweener(object, 'TextTransparency', object.TextTransparency, 1, 1, Util.Linear))
474 table.insert(this.FadeRoutines, Util.PropertyTweener(object, 'TextStrokeTransparency', object.TextStrokeTransparency, 1, 0.85, Util.Linear))
475 end
476 end
477 if this.MessageBackgroundImage then
478 this.MessageBackgroundImage.Visible = false
479 end
480 end
481 end
482
483 local function CreateMessageGuiElement()
484 local fontSize = this:GetMessageFontSize(this.Settings)
485
486 local systemMessageDisplayText = this.rawChatString or ""
487 local systemMessageSize = Util.GetStringTextBounds(systemMessageDisplayText, this.Settings.Font, fontSize, UDim2.new(0, 400, 0, 1000))
488
489 local container = Util.Create'Frame'
490 {
491 Name = 'MessageContainer';
492 Position = UDim2.new(0, 0, 0, 0);
493 ZIndex = 1;
494 BackgroundColor3 = Color3.new(0, 0, 0);
495 BackgroundTransparency = 1;
496 };
497 this.MessageBackgroundImage = Util.Create'ImageLabel'
498 {
499 Name = 'TextEntryBackground';
500 Size = UDim2.new(1,0,1,-2);
501 Position = UDim2.new(0,0,0,1);
502 Image = 'rbxasset://textures/ui/Chat/VRChatBackground.png';
503 ScaleType = Enum.ScaleType.Slice;
504 SliceCenter = Rect.new(8,8,56,56);
505 BackgroundTransparency = 1;
506 ImageTransparency = 0.3;
507 BorderSizePixel = 0;
508 ZIndex = 1;
509 Visible = InputService.VREnabled;
510 Parent = container;
511 }
512
513 local chatMessage = Util.Create'TextLabel'
514 {
515 Name = 'SystemChatMessage';
516 Position = UDim2.new(0, 0, 0, 0);
517 Size = UDim2.new(1, 0, 1, 0);
518 Text = systemMessageDisplayText;
519 ZIndex = 1;
520 BackgroundColor3 = Color3.new(0, 0, 0);
521 BackgroundTransparency = 1;
522 TextXAlignment = Enum.TextXAlignment.Left;
523 TextYAlignment = Enum.TextYAlignment.Top;
524 TextWrapped = true;
525 TextColor3 = this.Settings.DefaultMessageTextColor;
526 FontSize = fontSize;
527 Font = this.Settings.Font;
528 TextStrokeColor3 = this.Settings.TextStrokeColor;
529 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
530 Parent = container;
531 };
532 if InputService.VREnabled then
533 chatMessage.Position = UDim2.new(0, 4, 0, 0)
534 chatMessage.Size = UDim2.new(1, 0, 1, 0)
535 end
536
537 container.Size = UDim2.new(1, 0, 0, systemMessageSize.Y + 1);
538 this.Container = container
539 this.ChatMessage = chatMessage
540 end
541
542 CreateMessageGuiElement()
543
544 return this
545end
546
547--[[ Popup Handling ]]--
548function createPopupFrame(selectedPlayer, selectedButton)
549 if selectedPlayer and selectedPlayer.Parent == PlayersService then
550 if lastSelectedButton ~= selectedButton then
551 if lastSelectedButton ~= nil then
552 lastSelectedButton.BackgroundTransparency = 1
553 lastSelectedButton = nil
554 end
555 lastSelectedButton = selectedButton
556 lastSelectedPlayer = selectedPlayer
557 selectedButton.BackgroundTransparency = 0.5
558 else
559 lastSelectedPlayer = nil
560 end
561 end
562end
563
564function popupHidden()
565 if lastSelectedButton then
566 lastSelectedPlayer = nil
567 lastSelectedButton.BackgroundTransparency = 1
568 lastSelectedButton = nil
569 end
570end
571
572InputService.InputBegan:connect(function(inputObject, isProcessed)
573 if isProcessed then return end
574 local inputType = inputObject.UserInputType
575 if ((inputType == Enum.UserInputType.Touch and
576 inputObject.UserInputState == Enum.UserInputState.Begin) or
577 inputType == Enum.UserInputType.MouseButton1) then
578 end
579 end)
580
581--[[ End of popup handling ]]--
582
583local function CreatePlayerChatMessage(settings, playerChatType, sendingPlayer, chattedMessage, receivingPlayer)
584 local this = CreateChatMessage()
585
586 this.Settings = settings
587 this.PlayerChatType = playerChatType
588 this.SendingPlayer = sendingPlayer
589 this.RawMessageContent = chattedMessage
590 this.ReceivingPlayer = receivingPlayer
591 this.ReceivedTime = tick()
592
593 this.Neutral = this.SendingPlayer and this.SendingPlayer.Neutral or true
594 this.TeamColor = this.SendingPlayer and this.SendingPlayer.TeamColor or BrickColor.new("White")
595
596 function this:OnResize(containerSize)
597 if this.Container and this.ChatMessage then
598 this.Container.Size = UDim2.new(1,0,0,1000)
599 local textHeight = this.ChatMessage.TextBounds.Y
600 local newContainerHeight = textHeight + 5
601 this.Container.Size = UDim2.new(1,0,0,newContainerHeight)
602 return newContainerHeight
603 end
604 end
605
606 function this:FormatMessage()
607 local result = ""
608 if this.RawMessageContent then
609 local message = this.RawMessageContent
610 result = message
611 end
612 return result
613 end
614
615 function this:FormatChatType()
616 if this.PlayerChatType then
617 if this.PlayerChatType == Enum.PlayerChatType.All then
618 --return "[All]"
619 elseif this.PlayerChatType == Enum.PlayerChatType.Team then
620 return "[Team]"
621 elseif this.PlayerChatType == Enum.PlayerChatType.Whisper then
622 -- nothing!
623 end
624 end
625 end
626
627 function this:FormatPlayerNameText()
628 local playerName = ""
629 -- If we are sending a whisper to someone, then we should show their name
630 if this.PlayerChatType == Enum.PlayerChatType.Whisper and this.SendingPlayer and this.SendingPlayer == Player then
631 playerName = (this.ReceivingPlayer and this.ReceivingPlayer.Name or "")
632 else
633 playerName = (this.SendingPlayer and this.SendingPlayer.Name or "")
634 end
635 return "[" .. playerName .. "]:"
636 end
637
638 function this:FadeIn()
639 local gui = this:GetGui()
640 if gui then
641 gui.Visible = true
642 for _, routine in pairs(this.FadeRoutines) do
643 routine:Cancel()
644 end
645 this.FadeRoutines = {}
646 local tweenableObjects = {
647 this.WhisperToText;
648 this.WhisperFromText;
649 this.ChatModeButton;
650 this.UserNameButton;
651 this.ChatMessage;
652 }
653 for _, object in pairs(tweenableObjects) do
654 object.TextTransparency = 0;
655 object.TextStrokeTransparency = this.Settings.TextStrokeTransparency;
656 object.Active = true
657 end
658 if this.UserNameDot then
659 this.UserNameDot.ImageTransparency = 0
660 end
661
662 if this.MessageBackgroundImage then
663 this.MessageBackgroundImage.Visible = InputService.VREnabled
664 end
665 end
666 end
667
668 function this:FadeOut(instant)
669 local gui = this:GetGui()
670 if gui then
671 if instant then
672 gui.Visible = false
673 else
674 local tweenableObjects = {
675 this.WhisperToText;
676 this.WhisperFromText;
677 this.ChatModeButton;
678 this.UserNameButton;
679 this.ChatMessage;
680 }
681 for _, object in pairs(tweenableObjects) do
682 table.insert(this.FadeRoutines, Util.PropertyTweener(object, 'TextTransparency', object.TextTransparency, 1, 1, Util.Linear))
683 table.insert(this.FadeRoutines, Util.PropertyTweener(object, 'TextStrokeTransparency', object.TextStrokeTransparency, 1, 0.85, Util.Linear))
684 object.Active = false
685 end
686 if this.UserNameDot then
687 table.insert(this.FadeRoutines, Util.PropertyTweener(this.UserNameDot, 'ImageTransparency', this.UserNameDot.ImageTransparency, 1, 1, Util.Linear))
688 end
689 end
690 if this.MessageBackgroundImage then
691 this.MessageBackgroundImage.Visible = false
692 end
693 end
694 end
695
696 function this:Destroy()
697 if this.Container ~= nil then
698 this.Container:Destroy()
699 this.Container = nil
700 end
701 this.ClickedOnModeConn = Util.DisconnectEvent(this.ClickedOnModeConn)
702 this.ClickedOnPlayerConn = Util.DisconnectEvent(this.ClickedOnPlayerConn)
703 end
704
705 local function CreateMessageGuiElement()
706 local fontSize = this:GetMessageFontSize(this.Settings)
707
708 local toMesasgeDisplayText = "To "
709 local toMessageSize = Util.GetStringTextBounds(toMesasgeDisplayText, this.Settings.Font, fontSize)
710 local fromMesasgeDisplayText = "From "
711 local fromMessageSize = Util.GetStringTextBounds(fromMesasgeDisplayText, this.Settings.Font, fontSize)
712 local chatTypeDisplayText = this:FormatChatType()
713 local chatTypeSize = chatTypeDisplayText and Util.GetStringTextBounds(chatTypeDisplayText, this.Settings.Font, fontSize) or Vector2.new(0,0)
714 local playerNameDisplayText = this:FormatPlayerNameText()
715 local playerNameSize = Util.GetStringTextBounds(playerNameDisplayText, this.Settings.Font, fontSize)
716
717 local singleSpaceSize = Util.GetStringTextBounds(" ", this.Settings.Font, fontSize)
718 local numNeededSpaces = math.ceil(playerNameSize.X / singleSpaceSize.X) + 1
719 local chatMessageDisplayText = string.rep(" ", numNeededSpaces) .. this:FormatMessage()
720 local chatMessageSize = Util.GetStringTextBounds(chatMessageDisplayText, this.Settings.Font, fontSize, UDim2.new(0, 400 - 5 - playerNameSize.X, 0, 1000))
721
722
723 local playerColor = this.Settings.DefaultMessageTextColor
724 if this.SendingPlayer then
725 if this.PlayerChatType == Enum.PlayerChatType.Whisper then
726 if this.SendingPlayer == Player and this.ReceivingPlayer then
727 playerColor = Util.ComputeChatColor(this.ReceivingPlayer.Name)
728 else
729 playerColor = Util.ComputeChatColor(this.SendingPlayer.Name)
730 end
731 else
732 if this.SendingPlayer.Neutral then
733 playerColor = Util.ComputeChatColor(this.SendingPlayer.Name)
734 else
735 playerColor = this.SendingPlayer.TeamColor.Color
736 end
737 end
738 end
739
740 local container = Util.Create'Frame'
741 {
742 Name = 'MessageContainer';
743 Position = UDim2.new(0, 0, 0, 0);
744 ZIndex = 1;
745 BackgroundColor3 = Color3.new(0, 0, 0);
746 BackgroundTransparency = 1;
747 };
748 this.MessageBackgroundImage = Util.Create'ImageLabel'
749 {
750 Name = 'TextEntryBackground';
751 Size = UDim2.new(1,0,1,-2);
752 Position = UDim2.new(0,0,0,1);
753 Image = 'rbxasset://textures/ui/Chat/VRChatBackground.png';
754 ScaleType = Enum.ScaleType.Slice;
755 SliceCenter = Rect.new(8,8,56,56);
756 BackgroundTransparency = 1;
757 ImageTransparency = 0.3;
758 BorderSizePixel = 0;
759 ZIndex = 1;
760 Visible = InputService.VREnabled;
761 Parent = container;
762 }
763
764 local xOffset = InputService.VREnabled and 4 or 0
765
766 if this.SendingPlayer and this.SendingPlayer == Player and this.PlayerChatType == Enum.PlayerChatType.Whisper then
767 local whisperToText = Util.Create'TextLabel'
768 {
769 Name = 'WhisperTo';
770 Position = UDim2.new(0, 0, 0, 0);
771 Size = UDim2.new(0, toMessageSize.X, 0, toMessageSize.Y);
772 Text = toMesasgeDisplayText;
773 ZIndex = 1;
774 BackgroundColor3 = Color3.new(0, 0, 0);
775 BackgroundTransparency = 1;
776 TextXAlignment = Enum.TextXAlignment.Left;
777 TextYAlignment = Enum.TextYAlignment.Top;
778 TextWrapped = true;
779 TextColor3 = this.Settings.DefaultMessageTextColor;
780 FontSize = fontSize;
781 Font = this.Settings.Font;
782 TextStrokeColor3 = this.Settings.TextStrokeColor;
783 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
784 Parent = container;
785 };
786 xOffset = xOffset + toMessageSize.X
787 this.WhisperToText = whisperToText
788 elseif this.SendingPlayer and this.SendingPlayer ~= Player and this.PlayerChatType == Enum.PlayerChatType.Whisper then
789 local whisperFromText = Util.Create'TextLabel'
790 {
791 Name = 'WhisperFromText';
792 Position = UDim2.new(0, 0, 0, 0);
793 Size = UDim2.new(0, fromMessageSize.X, 0, fromMessageSize.Y);
794 Text = fromMesasgeDisplayText;
795 ZIndex = 1;
796 BackgroundColor3 = Color3.new(0, 0, 0);
797 BackgroundTransparency = 1;
798 TextXAlignment = Enum.TextXAlignment.Left;
799 TextYAlignment = Enum.TextYAlignment.Top;
800 TextWrapped = true;
801 TextColor3 = this.Settings.DefaultMessageTextColor;
802 FontSize = fontSize;
803 Font = this.Settings.Font;
804 TextStrokeColor3 = this.Settings.TextStrokeColor;
805 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
806 Parent = container;
807 };
808 xOffset = xOffset + fromMessageSize.X
809 this.WhisperFromText = whisperFromText
810 end
811 if chatTypeDisplayText then
812 local chatModeButton = Util.Create(Util.IsTouchDevice() and 'TextLabel' or 'TextButton')
813 {
814 Name = 'ChatMode';
815 BackgroundTransparency = 1;
816 ZIndex = 2;
817 Text = chatTypeDisplayText;
818 TextColor3 = this.Settings.DefaultMessageTextColor;
819 Position = UDim2.new(0, xOffset, 0, 0);
820 TextXAlignment = Enum.TextXAlignment.Left;
821 TextYAlignment = Enum.TextYAlignment.Top;
822 FontSize = fontSize;
823 Font = this.Settings.Font;
824 Size = UDim2.new(0, chatTypeSize.X, 0, chatTypeSize.Y);
825 TextStrokeColor3 = this.Settings.TextStrokeColor;
826 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
827 Parent = container
828 }
829 if chatModeButton:IsA('TextButton') then
830 this.ClickedOnModeConn = chatModeButton.MouseButton1Click:connect(function()
831 SelectChatModeEvent:fire(this.PlayerChatType)
832 end)
833 end
834 if this.PlayerChatType == Enum.PlayerChatType.Team then
835 chatModeButton.TextColor3 = playerColor
836 end
837 xOffset = xOffset + chatTypeSize.X + 1
838 this.ChatModeButton = chatModeButton
839 end
840 local userNameButton = Util.Create(Util.IsTouchDevice() and 'TextLabel' or 'TextButton')
841 {
842 Name = 'PlayerName';
843 BackgroundTransparency = 1;
844 BackgroundColor3 = Color3.new(0, 1, 1);
845 BorderSizePixel = 0;
846 ZIndex = 2;
847 Text = playerNameDisplayText;
848 TextColor3 = playerColor;
849 Position = UDim2.new(0, xOffset, 0, 0);
850 TextXAlignment = Enum.TextXAlignment.Left;
851 TextYAlignment = Enum.TextYAlignment.Top;
852 FontSize = fontSize;
853 Font = this.Settings.Font;
854 Size = UDim2.new(0, playerNameSize.X, 0, playerNameSize.Y);
855 TextStrokeColor3 = this.Settings.TextStrokeColor;
856 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
857 Parent = container
858 }
859 if userNameButton:IsA('TextButton') then
860 this.ClickedOnPlayerConn = userNameButton.MouseButton1Click:connect(function()
861 local gui = this:GetGui()
862 if gui and gui.Visible then
863 if this.PlayerChatType == Enum.PlayerChatType.Whisper and this.SendingPlayer == Player and this.ReceivingPlayer then
864 SelectPlayerEvent:fire(this.ReceivingPlayer)
865 else
866 SelectPlayerEvent:fire(this.SendingPlayer)
867 end
868 end
869 end)
870
871 end
872
873 local chatMessage = Util.Create'TextLabel'
874 {
875 Name = 'ChatMessage';
876 Position = UDim2.new(0, xOffset, 0, 0);
877 Size = UDim2.new(1, -xOffset, 1, 0);
878 Text = chatMessageDisplayText;
879 ZIndex = 1;
880 BackgroundColor3 = Color3.new(0, 0, 0);
881 BackgroundTransparency = 1;
882 TextXAlignment = Enum.TextXAlignment.Left;
883 TextYAlignment = Enum.TextYAlignment.Top;
884 TextWrapped = true;
885 TextColor3 = this.Settings.DefaultMessageTextColor;
886 FontSize = fontSize;
887 Font = this.Settings.Font;
888 TextStrokeColor3 = this.Settings.TextStrokeColor;
889 TextStrokeTransparency = this.Settings.TextStrokeTransparency;
890 Parent = container;
891 };
892 if InputService.VREnabled then
893 chatMessage.Size = chatMessage.Size - UDim2.new(0,4,0,0)
894 end
895 -- Check if they got moderated and put up a real message instead of Label
896 if chatMessage.Text == 'Label' and chatMessageDisplayText ~= 'Label' then
897 chatMessage.Text = string.rep(" ", numNeededSpaces) .. '[Content Deleted]'
898 end
899 if this.SendingPlayer then
900 if PlayerPermissionsModule.IsPlayerAdminAsync(this.SendingPlayer) then
901 chatMessage.TextColor3 = this.Settings.AdminTextColor
902 elseif PlayerPermissionsModule.IsPlayerInternAsync(this.SendingPlayer) then
903 chatMessage.TextColor3 = this.Settings.InternTextColor
904 end
905 end
906 chatMessage.Size = chatMessage.Size + UDim2.new(0, 0, 0, chatMessage.TextBounds.Y);
907
908 container.Size = UDim2.new(1, 0, 0, math.max(chatMessageSize.Y + 1, userNameButton.Size.Y.Offset + 1));
909 this.Container = container
910 this.ChatMessage = chatMessage
911 this.UserNameButton = userNameButton
912 end
913
914 CreateMessageGuiElement()
915
916 return this
917end
918
919local function CreateChatBarWidget(settings)
920 local this = {}
921
922 -- MessageModes: {All, Team, Whisper}
923 this.MessageMode = 'All'
924 this.TargetWhisperPlayer = nil
925 this.Settings = settings
926
927 this.WidgetVisible = false
928 this.FadedIn = true
929
930 this.ChatBarGainedFocusEvent = Util.Signal()
931 this.ChatBarLostFocusEvent = Util.Signal()
932 this.ChatCommandEvent = Util.Signal() -- Signal Signatue: success, actionType, [captures]
933 this.ChatErrorEvent = Util.Signal() -- Signal Signatue: success, actionType, [captures]
934 this.ChatBarFloodEvent = Util.Signal()
935
936 this.unfocusedAt = 0
937
938 local chatCoreGuiEnabled = true
939
940 -- This function while lets string.find work case-insensitively without clobbering the case of the captures
941 local function nocase(s)
942 s = string.gsub(s, "%a", function (c)
943 return string.format("[%s%s]", string.lower(c),
944 string.upper(c))
945 end)
946 return s
947 end
948
949 this.ChatMatchingRegex =
950 {
951 [function(chatBarText) return string.find(chatBarText, nocase("^/w ") .. "(%w+_?%w+)") end] = "Whisper";
952 [function(chatBarText) return string.find(chatBarText, nocase("^/whisper ") .. "(%w+_?%w+)") end] = "Whisper";
953
954 [function(chatBarText) return string.find(chatBarText, "^%%") end] = "Team";
955 [function(chatBarText) return string.find(chatBarText, "^%(TEAM%)") end] = "Team";
956 [function(chatBarText) return string.find(chatBarText, nocase("^/t")) end] = "Team";
957 [function(chatBarText) return string.find(chatBarText, nocase("^/team")) end] = "Team";
958
959 [function(chatBarText) return string.find(chatBarText, nocase("^/a")) end] = "All";
960 [function(chatBarText) return string.find(chatBarText, nocase("^/all")) end] = "All";
961 [function(chatBarText) return string.find(chatBarText, nocase("^/s")) end] = "All";
962 [function(chatBarText) return string.find(chatBarText, nocase("^/say")) end] = "All";
963
964 [function(chatBarText) return string.find(chatBarText, nocase("^/e")) end] = "Emote";
965 [function(chatBarText) return string.find(chatBarText, nocase("^/emote")) end] = "Emote";
966
967 [function(chatBarText) return string.find(chatBarText, "^/%?") end] = "Help";
968 [function(chatBarText) return string.find(chatBarText, nocase("^/help")) end] = "Help";
969
970 [function(chatBarText) return string.find(chatBarText, nocase("^/block ") .. "(%w+_?%w+)") end] = "Block";
971
972 [function(chatBarText) return string.find(chatBarText, nocase("^/unblock ") .. "(%w+_?%w+)") end] = "Unblock";
973
974 [function(chatBarText) return string.find(chatBarText, nocase("^/mute ") .. "(%w+_?%w+)") end] = "Mute";
975
976 [function(chatBarText) return string.find(chatBarText, nocase("^/unmute ") .. "(%w+_?%w+)") end] = "Unmute";
977 }
978
979 local ChatModesDict =
980 {
981 ['Whisper'] = 'Whisper';
982 ['Team'] = 'Team';
983 ['All'] = 'All';
984 [Enum.PlayerChatType.Whisper] = 'Whisper';
985 [Enum.PlayerChatType.Team] = 'Team';
986 [Enum.PlayerChatType.All] = 'All';
987 }
988
989 local function TearDownEvents()
990 this.ClickToChatButtonConn = Util.DisconnectEvent(this.ClickToChatButtonConn)
991 this.ChatBarFocusLostConn = Util.DisconnectEvent(this.ChatBarFocusLostConn)
992 this.ChatBarLostFocusConn = Util.DisconnectEvent(this.ChatBarLostFocusConn)
993 this.SelectChatModeConn = Util.DisconnectEvent(this.SelectChatModeConn)
994 this.SelectPlayerConn = Util.DisconnectEvent(this.SelectPlayerConn)
995 this.FocusChatBarInputBeganConn = Util.DisconnectEvent(this.FocusChatBarInputBeganConn)
996 this.InputBeganConn = Util.DisconnectEvent(this.InputBeganConn)
997 this.ChatBarChangedConn = Util.DisconnectEvent(this.ChatBarChangedConn)
998 end
999
1000 local function HookUpEvents()
1001 TearDownEvents() -- Cleanup old events
1002
1003 if this.ClickToChatButton then this.ClickToChatButtonConn = this.ClickToChatButton.MouseButton1Click:connect(function() this:FocusChatBar() end) end
1004
1005 if this.ChatBar then
1006 -- Use a count to check for double backspace out of a chatmode
1007 local count = 0
1008 if not Util.IsTouchDevice() then
1009 this.FocusChatBarInputBeganConn = Util.DisconnectEvent(this.FocusChatBarInputBeganConn)
1010 this.FocusChatBarInputBeganConn = InputService.InputBegan:connect(function(inputObj)
1011 if inputObj.KeyCode == Enum.KeyCode.Backspace and this:GetChatBarText() == "" then
1012 if count == 0 then
1013 count = count + 1
1014 else
1015 this:SetMessageMode('All')
1016 end
1017 else
1018 count = 0
1019 end
1020 end)
1021 end
1022
1023 this.ChatBarFocusLostConn = this.ChatBar.FocusLost:connect(function(...)
1024 count = 0
1025 this.unfocusedAt = tick()
1026 this.ChatBarLostFocusEvent:fire(...)
1027 end)
1028 this.ChatBarChangedConn = this.ChatBar.Changed:connect(function(prop)
1029 if prop == "Text" then
1030 this:OnChatBarTextChanged()
1031 elseif prop == 'TextFits' or prop == 'TextBounds' or prop == 'Visible' then
1032 this:OnChatBarBoundsChanged()
1033 end
1034 end)
1035 end
1036
1037 if this.ChatBarLostFocusEvent then this.ChatBarLostFocusConn = this.ChatBarLostFocusEvent:connect(function(...) this:OnChatBarFocusLost(...) end) end
1038
1039 this.SelectChatModeConn = SelectChatModeEvent:connect(function(chatType)
1040 this:SetMessageMode(chatType)
1041 this:FocusChatBar()
1042 end)
1043
1044 this.SelectPlayerConn = SelectPlayerEvent:connect(function(chatPlayer)
1045 this.TargetWhisperPlayer = chatPlayer
1046 this:SetMessageMode("Whisper")
1047 this:FocusChatBar()
1048 end)
1049
1050 this.InputBeganConn = InputService.InputBegan:connect(function(inputObject)
1051 if inputObject.KeyCode == Enum.KeyCode.Escape then
1052 -- Clear text when they press escape
1053 this:SetChatBarText("")
1054 end
1055 end)
1056 end
1057
1058 function this:CalculateVisibility()
1059 if this.ChatBarContainer then
1060 local enabled = self.WidgetVisible and chatCoreGuiEnabled and not NON_CORESCRIPT_MODE
1061 if enabled then
1062 HookUpEvents()
1063 else
1064 TearDownEvents()
1065 end
1066 this.ChatBarContainer.Visible = enabled and self.FadedIn and (not chatBarDisabled)
1067 end
1068 end
1069
1070 function this:ToggleVisibility(visible)
1071 if visible ~= self.WidgetVisible then
1072 self.WidgetVisible = visible
1073 self:CalculateVisibility()
1074 end
1075 if NON_CORESCRIPT_MODE or chatBarDisabled then
1076 this.ChatBarContainer.Visible = false
1077 end
1078 end
1079
1080 function this:FadeIn()
1081 self.FadedIn = true
1082 self:CalculateVisibility()
1083 end
1084
1085 function this:FadeOut()
1086 self.FadedIn = false
1087 self:CalculateVisibility()
1088 end
1089
1090 function this:CoreGuiChanged(coreGuiType, enabled)
1091 if coreGuiType == Enum.CoreGuiType.Chat or coreGuiType == Enum.CoreGuiType.All then
1092 chatCoreGuiEnabled = enabled
1093 self:CalculateVisibility()
1094 end
1095 end
1096
1097 function this:IsAChatMode(mode)
1098 return ChatModesDict[mode] ~= nil
1099 end
1100
1101 function this:ProcessChatBarModes(requireWhitespaceAfterChatMode)
1102 local matchedAChatCommand = false
1103 if this.ChatBar then
1104 local chatBarText = this:SanitizeInput(this:GetChatBarText())
1105 for regexFunc, actionType in pairs(this.ChatMatchingRegex) do
1106 local start, finish, capture = regexFunc(chatBarText)
1107 if start and finish then
1108 -- The following line is for whether or not to try setting the chatmode as-you-type
1109 -- versus when you press enter.
1110 local whitespaceAfterSlashCommand = string.find(string.sub(chatBarText, finish+1, finish+1), "%s")
1111 if (not requireWhitespaceAfterChatMode and finish == #chatBarText) or whitespaceAfterSlashCommand then
1112 if this:IsAChatMode(actionType) then
1113 if actionType == "Whisper" then
1114 local targetPlayer = capture and Util.GetPlayerByName(capture)
1115 if targetPlayer then --and targetPlayer ~= Player then
1116 this.TargetWhisperPlayer = targetPlayer
1117 -- start from two over to eat the space or tab character after the slash command
1118 this:SetChatBarText(string.sub(chatBarText, finish + 2))
1119 this:SetMessageMode(actionType)
1120 this.ChatCommandEvent:fire(true, actionType, capture)
1121 else
1122 -- This is an indirect way of detecting if they used enter to close submit this chat
1123 if not requireWhitespaceAfterChatMode then
1124 this:SetChatBarText("")
1125 this.ChatCommandEvent:fire(false, actionType, capture)
1126 end
1127 end
1128 else
1129 -- start from two over to eat the space or tab character after the slash command
1130 this:SetChatBarText(string.sub(chatBarText, finish + 2))
1131 this:SetMessageMode(actionType)
1132 this.ChatCommandEvent:fire(true, actionType, capture)
1133 end
1134 elseif actionType == "Emote" then
1135 -- You can only emote to everyone.
1136 this:SetMessageMode('All')
1137 elseif not requireWhitespaceAfterChatMode then -- Some non-chat related command
1138 if actionType == "Help" then
1139 this:SetChatBarText("") -- Clear the chat so we don't send /? to everyone
1140 end
1141 this.ChatCommandEvent:fire(true, actionType, capture)
1142 end
1143 -- should we break here since we already matched a slash command or keep going?
1144 matchedAChatCommand = true
1145 end
1146 end
1147 end
1148 end
1149 return matchedAChatCommand
1150 end
1151
1152 local previousText = ""
1153 function this:OnChatBarTextChanged()
1154 if not Util.IsTouchDevice() then
1155 this:ProcessChatBarModes(true)
1156 local originalText = this:GetChatBarText()
1157 local newText = Util.FilterUnprintableCharacters(originalText)
1158 if newText ~= originalText then
1159 previousText = newText
1160 end
1161
1162 local fixedText = newText
1163 if #newText > this.Settings.MaxCharactersInMessage or originalText ~= newText then
1164 -- This is a hack to deal with the bug that holding down a key for repeated input doesn't trigger the textChanged event
1165 if #newText == #previousText + 1 then
1166 fixedText = string.sub(previousText, 1, this.Settings.MaxCharactersInMessage)
1167 else
1168 fixedText = string.sub(newText, 1, this.Settings.MaxCharactersInMessage)
1169 end
1170 end
1171 this:SetChatBarText(fixedText)
1172 previousText = fixedText
1173 end
1174 end
1175
1176 function this:OnChatBarBoundsChanged()
1177 if this.ChatBarContainer and this.ChatBar then
1178 local currSize = this.ChatBarContainer.Size
1179 if this.ChatBar.Visible and not this.ChatBar.TextFits then
1180 local textBounds = Util.GetStringTextBounds(this.ChatBar.Text, this.ChatBar.Font, this.ChatBar.FontSize, UDim2.new(0, this.ChatBar.AbsoluteSize.X, 0, 1000))
1181 if textBounds.Y <= 36 then
1182 this.ChatBarContainer.Size = UDim2.new(currSize.X.Scale, currSize.X.Offset, currSize.Y.Scale, 58)
1183 else --if currSize.Y.Offset <= 54 then
1184 this.ChatBarContainer.Size = UDim2.new(currSize.X.Scale, currSize.X.Offset, currSize.Y.Scale, 76)
1185 end
1186 elseif this.ChatBar.Visible == false or this.ChatBar.TextBounds.Y <= 18 then
1187 if currSize.Y.Offset ~= 40 then
1188 this.ChatBarContainer.Size = UDim2.new(currSize.X.Scale, currSize.X.Offset, currSize.Y.Scale, 40)
1189 end
1190 elseif this.ChatBar.TextBounds.Y <= 36 then
1191 this.ChatBarContainer.Size = UDim2.new(currSize.X.Scale, currSize.X.Offset, currSize.Y.Scale, 58)
1192 end
1193 end
1194 end
1195
1196 function this:GetChatBarText()
1197 return this.ChatBar and this.ChatBar.Text or ""
1198 end
1199
1200 function this:SetChatBarText(newText)
1201 if this.ChatBar and newText ~= this.ChatBar.Text then
1202 this.ChatBar.Text = newText
1203 end
1204 end
1205
1206 function this:GetMessageMode()
1207 return this.MessageMode
1208 end
1209
1210 function this:SetMessageMode(newMessageMode)
1211 newMessageMode = ChatModesDict[newMessageMode]
1212
1213 local chatRecipientText = "[" .. (this.TargetWhisperPlayer and this.TargetWhisperPlayer.Name or "") .. "]"
1214 if this.MessageMode ~= newMessageMode or (newMessageMode == 'Whisper' and this.ChatModeText and chatRecipientText ~= this.ChatModeText.Text) then
1215 if this.ChatModeText then
1216 this.MessageMode = newMessageMode
1217 if newMessageMode == 'Whisper' then
1218 local chatRecipientTextBounds = Util.GetStringTextBounds(chatRecipientText, this.ChatModeText.Font, this.ChatModeText.FontSize)
1219
1220 this.ChatModeText.TextColor3 = this.Settings.WhisperTextColor
1221 this.ChatModeText.Text = chatRecipientText
1222 this.ChatModeText.Size = UDim2.new(0, chatRecipientTextBounds.X, 1, 0)
1223 elseif newMessageMode == 'Team' then
1224 local chatTeamText = '[Team]'
1225 local chatTeamTextBounds = Util.GetStringTextBounds(chatTeamText, this.ChatModeText.Font, this.ChatModeText.FontSize)
1226
1227 this.ChatModeText.TextColor3 = this.Settings.TeamTextColor
1228 this.ChatModeText.Text = "[Team]"
1229 this.ChatModeText.Size = UDim2.new(0, chatTeamTextBounds.X, 1, 0)
1230 else
1231 this.ChatModeText.Text = ""
1232 this.ChatModeText.Size = UDim2.new(0, 0, 1, 0)
1233 end
1234 if this.ChatBar then
1235 local offset = this.ChatModeText.Size.X.Offset + this.ChatModeText.Position.X.Offset
1236 this.ChatBar.Size = UDim2.new(1, -14 - offset, 1, 0)
1237 this.ChatBar.Position = UDim2.new(0, 7 + offset, 0, 0)
1238 end
1239 end
1240 end
1241 end
1242
1243 function this:FocusChatBar()
1244 if this.ChatBar and not chatBarDisabled then
1245 this.ChatBar.Visible = true
1246 this.ChatBar:CaptureFocus()
1247 if self.ClickToChatButton then
1248 self.ClickToChatButton.Visible = false
1249 end
1250 if this.ChatModeText then
1251 this.ChatModeText.Visible = true
1252 end
1253 if Util.IsTouchDevice() or InputService.VREnabled then
1254 this:SetMessageMode('All') -- Don't remember message mode on mobile devices or VR
1255 end
1256 -- Update chatbar properties when chatbar is focused
1257 this:OnChatBarBoundsChanged()
1258 if this.ChatBarContainer then
1259 if self.ChatBarInnerBackground then
1260 self.ChatBarInnerBackground.BackgroundTransparency = 0
1261 end
1262 end
1263 this.ChatBarGainedFocusEvent:fire()
1264 end
1265 end
1266
1267 function this:RemoveFocus()
1268 if self:IsFocused() then
1269 self.ChatBar:ReleaseFocus()
1270 end
1271 end
1272
1273 function this:IsFocused()
1274 return self.ChatBar and self.ChatBar == InputService:GetFocusedTextBox()
1275 end
1276
1277 function this:WasFocused()
1278 return (tick() - this.unfocusedAt) < VR_CHAT_CLICK_DEBOUNCE
1279 end
1280
1281 function this:SanitizeInput(input)
1282 local sanitizedInput = input
1283 -- Chomp the whitespace at the front and end of the string
1284 -- TODO: maybe only chop off the front space if there are more than a few?
1285 local _, _, capture = string.find(sanitizedInput, "^%s*(.*)%s*$")
1286 sanitizedInput = capture or ""
1287
1288 return sanitizedInput
1289 end
1290
1291
1292 local sentMessageTimeQueue = {}
1293 function this:FloodCheck()
1294 if not GetLuaChatFilteringFlag() then
1295 return false
1296 end
1297
1298 while sentMessageTimeQueue[1] and tick() - sentMessageTimeQueue[1] > FLOOD_CHECK_MESSAGE_INTERVAL do
1299 table.remove(sentMessageTimeQueue, 1)
1300 end
1301 if #sentMessageTimeQueue > FLOOD_CHECK_MESSAGE_COUNT then
1302 return true
1303 end
1304 return false
1305 end
1306
1307 function this:OnChatBarFocusLost(enterPressed)
1308 if self.ChatBar then
1309 self.ChatBar.Visible = false
1310 if enterPressed then
1311 local didMatchSlashCommand = self:ProcessChatBarModes(false)
1312 local cText = self:SanitizeInput(self:GetChatBarText())
1313 if cText ~= "" then
1314 if self:FloodCheck() then -- and not didMatchSlashCommand then
1315 self.ChatBarFloodEvent:fire()
1316 else
1317 -- For now we will let any slash command go through, NOTE: these will show up in bubble-chat
1318 --if not didMatchSlashCommand and string.sub(cText,1,1) == "/" then
1319 -- self.ChatCommandEvent:fire(false, "Unknown", cText)
1320 --else
1321 local currentMessageMode = self:GetMessageMode()
1322 -- {All, Team, Whisper}
1323 if currentMessageMode == 'Team' then
1324 if Player and Player.Neutral == true then
1325 self.ChatErrorEvent:fire("You're not on a team.")
1326 else
1327 pcall(function() PlayersService:TeamChat(cText) end)
1328 end
1329 elseif currentMessageMode == 'Whisper' then
1330 if self.TargetWhisperPlayer then
1331 if self.TargetWhisperPlayer == Player then
1332 self.ChatErrorEvent:fire("You cannot send a whisper to yourself.")
1333 else
1334 pcall(function() PlayersService:WhisperChat(cText, self.TargetWhisperPlayer) end)
1335 end
1336 else
1337 self.ChatErrorEvent:fire("Invalid whisper target.")
1338 end
1339 elseif currentMessageMode == 'All' then
1340 pcall(function() PlayersService:Chat(cText) end)
1341 else
1342 spawn(function() error("ChatScript: Unknown Message Mode of " .. tostring(currentMessageMode)) end)
1343 end
1344 table.insert(sentMessageTimeQueue, tick())
1345 --end
1346 self:SetChatBarText("")
1347 end
1348 end
1349 end
1350 end
1351 if self.ClickToChatButton then
1352 self.ClickToChatButton.Visible = true
1353 -- Fade-back in the text so it doesn't abruptly appear
1354 -- Normally I would like to cancel the old tween but it is so short that it doesn't matter
1355 self.ClickToChatButton.TextTransparency = 1
1356 Util.PropertyTweener(self.ClickToChatButton, 'TextTransparency', 1, 0, 0.25, Util.Linear)
1357 end
1358 if self.ChatModeText then
1359 self.ChatModeText.Visible = false
1360 end
1361 if this.ChatBarContainer then
1362 local currSize = this.ChatBarContainer.Size
1363 this.ChatBarContainer.Size = UDim2.new(currSize.X.Scale, currSize.X.Offset, currSize.Y.Scale, 32)
1364 if self.ChatBarInnerBackground then
1365 self.ChatBarInnerBackground.BackgroundTransparency = 0.5
1366 end
1367 end
1368 this.ChatBarChangedConn = Util.DisconnectEvent(this.ChatBarChangedConn)
1369 this.FocusChatBarInputBeganConn = Util.DisconnectEvent(this.FocusChatBarInputBeganConn)
1370 end
1371
1372 local function CreateChatBar()
1373 local chatBarContainer = Util.Create'Frame'
1374 {
1375 Name = 'ChatBarContainer';
1376 Position = UDim2.new(0, 0, 1, 0);
1377 Size = UDim2.new(1, 0, 0, 20);
1378 ZIndex = 1;
1379 BackgroundColor3 = Color3.new(0, 0, 0);
1380 BackgroundTransparency = 0.25;
1381 BorderSizePixel = 0;
1382 };
1383 chatBarContainer.BackgroundColor3 = Color3.new(31/255, 31/255, 31/255);
1384 chatBarContainer.BackgroundTransparency = 0.5;
1385 local chatBarInnerBackground = Util.Create'Frame'
1386 {
1387 Name = 'InnerBackground';
1388 Position = UDim2.new(0, 7, 0, 5);
1389 Size = UDim2.new(1, -14, 1, -10);
1390 ZIndex = 1;
1391 BackgroundColor3 = Color3.new(209/255, 216/255, 221/255);
1392 BackgroundTransparency = 0.5;
1393 BorderSizePixel = 0;
1394 };
1395 local clickToChatButton = Util.Create'TextButton'
1396 {
1397 Name = 'ClickToChat';
1398 Position = UDim2.new(0,9,0,0);
1399 Size = UDim2.new(1, -9, 1, 0);
1400 BackgroundTransparency = 1;
1401 AutoButtonColor = false;
1402 ZIndex = 3;
1403 Text = 'To chat click here or press "/" key';
1404 TextColor3 = this.Settings.GlobalTextColor;
1405 TextXAlignment = Enum.TextXAlignment.Left;
1406 TextYAlignment = Enum.TextYAlignment.Top;
1407 Font = Enum.Font.SourceSansBold;
1408 FontSize = Enum.FontSize.Size18;
1409 Parent = chatBarContainer;
1410 }
1411 clickToChatButton.TextWrapped = true;
1412 clickToChatButton.Position = UDim2.new(0, 7, 0, 0);
1413 clickToChatButton.Size = UDim2.new(1, -14, 1, 0);
1414 clickToChatButton.TextYAlignment = Enum.TextYAlignment.Center;
1415 if Util.IsTouchDevice() then
1416 clickToChatButton.Text = "Tap here to chat"
1417 end
1418
1419 local chatBar = Util.Create'TextBox'
1420 {
1421 Name = 'ChatBar';
1422 Position = UDim2.new(0, 9, 0, 0);
1423 Size = UDim2.new(1, -9, 1, 0);
1424 Text = "";
1425 ZIndex = 1;
1426 BackgroundColor3 = Color3.new(0, 0, 0);
1427 Active = false;
1428 BackgroundTransparency = 1;
1429 TextXAlignment = Enum.TextXAlignment.Left;
1430 TextYAlignment = Enum.TextYAlignment.Top;
1431 TextColor3 = this.Settings.GlobalTextColor;
1432 Font = Enum.Font.SourceSansBold;
1433 FontSize = Enum.FontSize.Size18;
1434 ClearTextOnFocus = false;
1435 Visible = not Util.IsTouchDevice();
1436 Parent = chatBarContainer;
1437 SelectionImageObject = emptySelectionImage;
1438 }
1439 chatBar.TextWrapped = true;
1440 chatBar.Position = UDim2.new(0, 7, 0, 0);
1441 chatBar.Size = UDim2.new(1, -14, 1, 0);
1442 chatBar.TextYAlignment = Enum.TextYAlignment.Center;
1443 chatBar.Visible = false;
1444
1445 local chatModeText = Util.Create'TextButton'
1446 {
1447 Name = 'ChatModeText';
1448 Position = UDim2.new(0, 9, 0, 0);
1449 Size = UDim2.new(1, -9, 1, 0);
1450 AutoButtonColor = false;
1451 BackgroundTransparency = 1;
1452 ZIndex = 2;
1453 Text = '';
1454 TextColor3 = this.Settings.WhisperTextColor;
1455 TextXAlignment = Enum.TextXAlignment.Left;
1456 TextYAlignment = Enum.TextYAlignment.Top;
1457 Font = Enum.Font.SourceSansBold;
1458 FontSize = Enum.FontSize.Size18;
1459 Parent = chatBarContainer;
1460 }
1461 chatModeText.Position = UDim2.new(0, 7, 0, 0);
1462 chatModeText.Size = UDim2.new(1, -14, 1, 0);
1463 chatModeText.TextYAlignment = Enum.TextYAlignment.Center;
1464 -- Create grey background for text
1465 chatBarInnerBackground.Parent = chatBarContainer;
1466 clickToChatButton.Parent = chatBarInnerBackground;
1467 chatBar.Parent = chatBarInnerBackground;
1468 chatModeText.Parent = chatBarInnerBackground;
1469
1470 this.ChatBarContainer = chatBarContainer
1471 this.ChatBarInnerBackground = chatBarInnerBackground
1472 this.ClickToChatButton = clickToChatButton
1473 this.ChatBar = chatBar
1474 this.ChatModeText = chatModeText
1475 this.ChatBarContainer.Parent = GuiRoot
1476
1477 local function UpdateChatBarContainerLayout(newSize)
1478 if chatBarContainer then
1479 local chatbarVisible = this.ChatBar and this.ChatBar.Visible
1480 local bubbleChatIsOn = not PlayersService.ClassicChat and PlayersService.BubbleChat
1481 -- Phone
1482 if newSize.X <= PHONE_SCREEN_WIDTH then
1483 chatBarContainer.Size = UDim2.new(0.5, 0,0, chatbarVisible and 40 or 32)
1484 if bubbleChatIsOn then
1485 chatBarContainer.Position = UDim2.new(0, 0, 0, 2)
1486 else
1487 chatBarContainer.Position = UDim2.new(0, 0, 0.5, 2)
1488 end
1489 -- Tablet
1490 elseif newSize.X <= TABLET_SCREEN_WIDTH then
1491 chatBarContainer.Size = UDim2.new(0.4, 0,0, chatbarVisible and 40 or 32)
1492 if bubbleChatIsOn then
1493 chatBarContainer.Position = UDim2.new(0, 0, 0, 2)
1494 else
1495 chatBarContainer.Position = UDim2.new(0, 0, 0.3, 2)
1496 end
1497 -- Desktop
1498 else
1499 chatBarContainer.Size = UDim2.new(0.3, 0,0, chatbarVisible and 40 or 32)
1500 if bubbleChatIsOn then
1501 chatBarContainer.Position = UDim2.new(0, 0, 0, 2)
1502 else
1503 chatBarContainer.Position = UDim2.new(0,0,0.25, 2)
1504 end
1505 end
1506
1507 if Util.IsTouchDevice() or InputService.VREnabled then
1508 -- Hide the chatbar on mobile and in VR so they can't see it.
1509 chatBarContainer.Position = UDim2.new(0,0,1,20);
1510 end
1511 end
1512 end
1513
1514 GuiRoot.Changed:connect(function(prop)
1515 if (prop == "AbsoluteSize" and not chatRepositioned) then
1516 UpdateChatBarContainerLayout(GuiRoot.AbsoluteSize)
1517 end
1518 end)
1519 UpdateChatBarContainerLayout(GuiRoot.AbsoluteSize)
1520 end
1521
1522
1523 CreateChatBar()
1524 return this
1525end
1526
1527local function CreateChatWindowWidget(settings)
1528 local this = {}
1529 this.Settings = settings
1530 this.Chats = {}
1531 this.BackgroundVisible = false
1532 this.ChatsVisible = false
1533 this.WidgetVisible = false
1534 this.NewUnreadMessage = false
1535 this.MessageCount = 0
1536
1537 this.MessageCountChanged = Util.Signal()
1538 this.FadeInSignal = Util.Signal()
1539 this.FadeOutSignal = Util.Signal()
1540
1541 this.ChatWindowPagingConn = nil
1542
1543 local lastMoveTime = tick()
1544 local lastEnterTime = tick()
1545 local lastLeaveTime = tick()
1546
1547 local lastFadeOutTime = 0
1548 local lastFadeInTime = 0
1549 local lastChatActivity = 0
1550
1551 local FadeLock = false
1552
1553 local chatCoreGuiEnabled = true
1554
1555 local function PointInChatWindow(pt)
1556 local point0 = this.ChatContainer.AbsolutePosition
1557 local point1 = point0 + this.ChatContainer.AbsoluteSize
1558 -- HACK, this is so the "ChatWindow" includes the chatbar box, TODO: refactor the fadeing code to include the chatbar
1559 point1 = point1 + Vector2.new(0, 34)
1560 return (point0.X <= pt.X and
1561 point1.X >= pt.X and
1562 point0.Y <= pt.Y and
1563 point1.Y >= pt.Y)
1564 end
1565
1566 function this:IsHovering()
1567 if this.ChatContainer and this.LastMousePosition and self:CalculateVisibility() then
1568 return PointInChatWindow(this.LastMousePosition)
1569 end
1570 return false
1571 end
1572
1573 function this:SetFadeLock(lock)
1574 FadeLock = lock
1575 end
1576
1577 function this:GetFadeLock()
1578 return FadeLock
1579 end
1580
1581 function this:SetCanvasPosition(newCanvasPosition)
1582 if this.ScrollingFrame then
1583 local maxSize = Vector2.new(math.max(0, this.ScrollingFrame.CanvasSize.X.Offset - this.ScrollingFrame.AbsoluteWindowSize.X),
1584 math.max(0, this.ScrollingFrame.CanvasSize.Y.Offset - this.ScrollingFrame.AbsoluteWindowSize.Y))
1585 this.ScrollingFrame.CanvasPosition = Vector2.new(Util.Clamp(0, maxSize.X, newCanvasPosition.X),
1586 Util.Clamp(0, maxSize.Y, newCanvasPosition.Y))
1587 end
1588 end
1589
1590 function this:ScrollToBottom()
1591 if this.ScrollingFrame then
1592 this:SetCanvasPosition(Vector2.new(this.ScrollingFrame.CanvasPosition.X, this.ScrollingFrame.CanvasSize.Y.Offset))
1593 end
1594 end
1595
1596 function this:FadeIn(duration, lockFade)
1597 if not FadeLock then
1598 duration = duration or 0.75
1599 local backgroundTransparency = InputService.VREnabled and 1 or 0.5
1600 -- fade in
1601 if this.BackgroundTweener then
1602 this.BackgroundTweener:Cancel()
1603 end
1604 lastFadeInTime = tick()
1605 lastChatActivity = tick()
1606 this.ScrollingFrame.ScrollingEnabled = true
1607 this.ScrollingFrame.ScrollBarThickness = SCROLLBAR_THICKNESS
1608 this.BackgroundTweener = Util.PropertyTweener(this.ChatContainer, 'BackgroundTransparency', this.ChatContainer.BackgroundTransparency, backgroundTransparency, duration, Util.Linear)
1609 this.BackgroundVisible = true
1610 this:FadeInChats()
1611
1612 this.ChatWindowPagingConn = Util.DisconnectEvent(this.ChatWindowPagingConn)
1613 this.ChatWindowPagingConn = InputService.InputBegan:connect(function(inputObject)
1614 local key = inputObject.KeyCode
1615 if key == Enum.KeyCode.PageUp then
1616 this:SetCanvasPosition(this.ScrollingFrame.CanvasPosition - Vector2.new(0, this.ScrollingFrame.AbsoluteWindowSize.Y))
1617 elseif key == Enum.KeyCode.PageDown then
1618 this:SetCanvasPosition(this.ScrollingFrame.CanvasPosition + Vector2.new(0, this.ScrollingFrame.AbsoluteWindowSize.Y))
1619 elseif key == Enum.KeyCode.Home then
1620 this:SetCanvasPosition(Vector2.new(0, 0))
1621 elseif key == Enum.KeyCode.End then
1622 this:ScrollToBottom()
1623 end
1624 end)
1625 if this.FadeInSignal then
1626 this.FadeInSignal:fire()
1627 end
1628 end
1629 end
1630
1631 function this:FadeOut(duration, unlockFade)
1632 if not FadeLock then
1633 duration = duration or 0.75
1634 -- fade out
1635 if this.BackgroundTweener then
1636 this.BackgroundTweener:Cancel()
1637 end
1638 lastFadeOutTime = tick()
1639 lastChatActivity = tick()
1640 this.ScrollingFrame.ScrollingEnabled = false
1641 this.ScrollingFrame.ScrollBarThickness = 0
1642 this.BackgroundTweener = Util.PropertyTweener(this.ChatContainer, 'BackgroundTransparency', this.ChatContainer.BackgroundTransparency, 1, duration, Util.Linear)
1643 this.BackgroundVisible = false
1644
1645 this.ChatWindowPagingConn = Util.DisconnectEvent(this.ChatWindowPagingConn)
1646 if this.FadeOutSignal then
1647 this.FadeOutSignal:fire()
1648 end
1649 end
1650 end
1651
1652 function this:FadeInChats()
1653 if this.ChatsVisible == true then return end
1654 this.ChatsVisible = true
1655 for index, message in pairs(this.Chats) do
1656 message:FadeIn()
1657 end
1658 end
1659
1660 function this:FadeOutChats()
1661 if InputService.VREnabled then return end
1662 if this.ChatsVisible == false then return end
1663 this.ChatsVisible = false
1664 for index, message in pairs(this.Chats) do
1665 local messageGui = message:GetGui()
1666 local instant = false
1667 if messageGui and this.ScrollingFrame then
1668 -- If the chat is not in the visible frame then don't waste cpu cycles fading it out
1669 if (messageGui.AbsolutePosition.Y > (this.ScrollingFrame.AbsolutePosition + this.ScrollingFrame.AbsoluteWindowSize).Y or
1670 messageGui.AbsolutePosition.Y + messageGui.AbsoluteSize.Y < this.ScrollingFrame.AbsolutePosition.Y) then
1671 instant = true
1672 end
1673 end
1674 message:FadeOut(instant)
1675 end
1676 end
1677
1678 local ResizeCount = 0
1679 function this:OnResize()
1680 ResizeCount = ResizeCount + 1
1681 local currentResizeCount = ResizeCount
1682 local isScrolledDown = this:IsScrolledDown()
1683 -- Unfortunately there is a race condition so we need this wait here.
1684 wait()
1685 if this.ScrollingFrame then
1686 if currentResizeCount ~= ResizeCount then return end
1687 local scrollingFrameAbsoluteSize = this.ScrollingFrame.AbsoluteWindowSize
1688 if scrollingFrameAbsoluteSize ~= nil and scrollingFrameAbsoluteSize.X > 0 and scrollingFrameAbsoluteSize.Y > 0 then
1689 local ySize = 0
1690
1691 if this.ScrollingFrame then
1692 for _, message in pairs(this.Chats) do
1693 local newHeight = message:OnResize(scrollingFrameAbsoluteSize)
1694 if newHeight then
1695 local chatMessageElement = message:GetGui()
1696 if chatMessageElement then
1697 local chatMessageElementYSize = chatMessageElement.Size.Y.Offset
1698 chatMessageElement.Position = UDim2.new(0, 0, 0, ySize)
1699 ySize = ySize + chatMessageElementYSize
1700 end
1701 end
1702 end
1703 end
1704 if this.MessageContainer and this.ScrollingFrame then
1705 this.MessageContainer.Size = UDim2.new(
1706 this.MessageContainer.Size.X.Scale,
1707 this.MessageContainer.Size.X.Offset,
1708 0,
1709 ySize)
1710 this.MessageContainer.Position = UDim2.new(0, 0, 1, -this.MessageContainer.Size.Y.Offset)
1711 this.ScrollingFrame.CanvasSize = UDim2.new(this.ScrollingFrame.CanvasSize.X.Scale, this.ScrollingFrame.CanvasSize.X.Offset, this.ScrollingFrame.CanvasSize.Y.Scale, ySize)
1712 end
1713 end
1714 this:ScrollToBottom()
1715 end
1716 end
1717
1718 function this:FilterMessage(playerChatType, sendingPlayer, chattedMessage, receivingPlayer)
1719 if chattedMessage and string.sub(chattedMessage, 1, 1) ~= '/' then
1720 return true
1721 end
1722 return false
1723 end
1724
1725 function this:PushMessageIntoQueue(chatMessage, silently)
1726 table.insert(this.Chats, chatMessage)
1727
1728 local isScrolledDown = this:IsScrolledDown()
1729
1730 local chatMessageElement = chatMessage:GetGui()
1731
1732 chatMessageElement.Parent = this.MessageContainer
1733 local chatMessageHeight = chatMessage:OnResize() or 10
1734 local ySize = this.MessageContainer.Size.Y.Offset
1735 local chatMessageElementYSize = UDim2.new(0, 0, 0, chatMessageHeight)
1736
1737 if not silently then
1738 this.MessageCount = this.MessageCount + 1
1739 end
1740
1741 chatMessageElement.Position = chatMessageElement.Position + UDim2.new(0, 0, 0, ySize)
1742 this.MessageContainer.Size = this.MessageContainer.Size + chatMessageElementYSize
1743 this.ScrollingFrame.CanvasSize = this.ScrollingFrame.CanvasSize + chatMessageElementYSize
1744
1745 if this.Settings.MaxWindowChatMessages < #this.Chats then
1746 this:RemoveOldestMessage()
1747 end
1748 if isScrolledDown then
1749 this:ScrollToBottom()
1750 elseif not silently then
1751 -- Raise unread message alert!
1752 this.NewUnreadMessage = true
1753 end
1754
1755 if silently then
1756 if this.ChatsVisible == false then
1757 chatMessage:FadeOut(true)
1758 end
1759 else
1760 this:FadeInChats()
1761 lastChatActivity = tick()
1762 this.MessageCountChanged:fire(this.MessageCount)
1763 end
1764
1765 -- NOTE: Sort of hacky, but if we are approaching the max 16 bit size
1766 -- we need to rebase y back to 0 which can be done with the resize function
1767 if ySize > (MAX_UDIM_SIZE / 2) then
1768 self:OnResize()
1769 end
1770 end
1771
1772 function this:AddSystemChatMessage(chattedMessage, silently)
1773 local chatMessage = CreateSystemChatMessage(this.Settings, chattedMessage)
1774 this:PushMessageIntoQueue(chatMessage, silently)
1775 end
1776
1777 local function checkEnum(enumItems, value)
1778 for _, enum in pairs(enumItems) do
1779 if enum.Value == value then
1780 return enum
1781 end
1782 end
1783 return nil
1784 end
1785
1786 -- We only need to copy the top level for the settings table
1787 local function shallowCopy(tableToCopy)
1788 local newTable = {}
1789 for key, value in pairs(tableToCopy) do
1790 newTable[key] = value
1791 end
1792 return newTable
1793 end
1794
1795 function this:AddDeveloperSystemChatMessage(informationTable)
1796 local settings = shallowCopy(this.Settings)
1797
1798 if informationTable["Text"] and type(informationTable["Text"]) == "string" then
1799 if typeof(informationTable.Color) == "Color3" then
1800 settings.DefaultMessageTextColor = informationTable.Color
1801 end
1802 if typeof(informationTable.Font) == "EnumItem" and informationTable.Font.EnumType == Enum.Font then
1803 settings.Font = informationTable.Font
1804 end
1805 if typeof(informationTable.FontSize) == "EnumItem" and informationTable.FontSize.EnumType == Enum.FontSize then
1806 settings.FontSize = informationTable.FontSize
1807 end
1808 local chatMessage = CreateSystemChatMessage(settings, informationTable["Text"])
1809 this:PushMessageIntoQueue(chatMessage, false)
1810 end
1811 end
1812
1813 function this:AddChatMessage(playerChatType, sendingPlayer, chattedMessage, receivingPlayer, silently)
1814 local fixedChattedMessage = Util.FilterUnprintableCharacters(chattedMessage)
1815 if this:FilterMessage(playerChatType, sendingPlayer, fixedChattedMessage, receivingPlayer) then
1816 local chatMessage = CreatePlayerChatMessage(this.Settings, playerChatType, sendingPlayer, fixedChattedMessage, receivingPlayer)
1817 this:PushMessageIntoQueue(chatMessage, silently)
1818 end
1819 end
1820
1821 function this:RemoveOldestMessage()
1822 local oldestChat = this.Chats[1]
1823 if oldestChat then
1824 return this:RemoveChatMessage(oldestChat)
1825 end
1826 end
1827
1828 function this:RemoveChatMessage(chatMessage)
1829 if chatMessage then
1830 for index, message in pairs(this.Chats) do
1831 if chatMessage == message then
1832 local guiObj = chatMessage:GetGui()
1833 if guiObj then
1834 local ySize = guiObj.Size.Y.Offset
1835 this.ScrollingFrame.CanvasSize = this.ScrollingFrame.CanvasSize - UDim2.new(0,0,0,ySize)
1836 -- Clamp the canvasposition
1837 this:SetCanvasPosition(this.ScrollingFrame.CanvasPosition)
1838 guiObj.Parent = nil
1839 end
1840 message:Destroy()
1841 return table.remove(this.Chats, index)
1842 end
1843 end
1844 end
1845 end
1846
1847 function this:IsScrolledDown()
1848 if this.ScrollingFrame then
1849 local yCanvasSize = this.ScrollingFrame.CanvasSize.Y.Offset
1850 local yContainerSize = this.ScrollingFrame.AbsoluteWindowSize.Y
1851 local yScrolledPosition = this.ScrollingFrame.CanvasPosition.Y
1852 -- Check if the messages are at the bottom
1853 return yCanvasSize < yContainerSize or
1854 yCanvasSize - yScrolledPosition <= yContainerSize + 5 -- a little wiggle room
1855 end
1856 return false
1857 end
1858
1859 function this:GetMessageCount()
1860 return this.MessageCount
1861 end
1862
1863 function this:CalculateVisibility()
1864 return this.WidgetVisible and ((chatCoreGuiEnabled and PlayersService.ClassicChat) or NON_CORESCRIPT_MODE)
1865 end
1866
1867 function this:ToggleVisibility(visible)
1868 if visible ~= self.WidgetVisible then
1869 self.WidgetVisible = visible
1870 if this.ChatContainer then
1871 this.ChatContainer.Visible = self:CalculateVisibility()
1872 end
1873 end
1874 if NON_CORESCRIPT_MODE then
1875 this.ChatContainer.Visible = true
1876 end
1877 end
1878
1879 function this:CoreGuiChanged(coreGuiType, enabled)
1880 if coreGuiType == Enum.CoreGuiType.Chat or coreGuiType == Enum.CoreGuiType.All then
1881 chatCoreGuiEnabled = enabled
1882 if this.ChatContainer then
1883 this.ChatContainer.Visible = self:CalculateVisibility()
1884 end
1885 end
1886 end
1887
1888 local function CreateChatWindow()
1889 -- This really shouldn't be a button, but it is currently needed for VR.
1890 local container = Util.Create 'TextButton'
1891 {
1892 Name = 'ChatWindowContainer';
1893 Size = UDim2.new(0.3, 0, 0.25, 0);
1894 ZIndex = 1;
1895 BackgroundColor3 = Color3.new(0, 0, 0);
1896 BackgroundTransparency = 1;
1897 BorderSizePixel = 0;
1898 SelectionImageObject = emptySelectionImage;
1899 Active = false;
1900 Text = ""
1901 };
1902
1903 container.BackgroundColor3 = Color3.new(31/255, 31/255, 31/255);
1904 local scrollingFrame = Util.Create'ScrollingFrame'
1905 {
1906 Name = 'ChatWindow';
1907 Size = UDim2.new(1, -4 - 10, 1, -20);
1908 CanvasSize = UDim2.new(1, -4 - 10, 0, 0);
1909 Position = UDim2.new(0, 10, 0, 10);
1910 ZIndex = 1;
1911 BackgroundColor3 = Color3.new(0, 0, 0);
1912 BackgroundTransparency = 1;
1913 BottomImage = "rbxasset://textures/ui/scroll-bottom.png";
1914 MidImage = "rbxasset://textures/ui/scroll-middle.png";
1915 TopImage = "rbxasset://textures/ui/scroll-top.png";
1916 ScrollBarThickness = 0;
1917 BorderSizePixel = 0;
1918 ScrollingEnabled = false;
1919 Parent = container;
1920 };
1921 local messageContainer = Util.Create'Frame'
1922 {
1923 Name = 'MessageContainer';
1924 Size = UDim2.new(1, -SCROLLBAR_THICKNESS - 1, 0, 0);
1925 Position = UDim2.new(0, 0, 1, 0);
1926 ZIndex = 1;
1927 BackgroundColor3 = Color3.new(0, 0, 0);
1928 BackgroundTransparency = 1;
1929 Parent = scrollingFrame
1930 };
1931
1932 local function OnChatWindowResize(prop)
1933 if prop == 'AbsoluteSize' then
1934 messageContainer.Position = UDim2.new(0, 0, 1, -messageContainer.Size.Y.Offset)
1935 end
1936 if prop == 'CanvasPosition' then
1937 if this.ScrollingFrame then
1938 if this:IsScrolledDown() then
1939 this.NewUnreadMessage = false
1940 end
1941 end
1942 end
1943 end
1944
1945 container.Changed:connect(function(prop)
1946 if prop == 'AbsoluteSize' then
1947 this:OnResize()
1948 end
1949 end)
1950
1951 local function UpdateChatWindowLayout(newSize)
1952 -- A function to position the chat window in light of various factors
1953 -- (platform, container window size, presence of performance stats).
1954 if container == nil then
1955 return
1956 end
1957
1958 -- Account for presence/absence of performance stats buttons.
1959 local localPlayer = PlayersService.LocalPlayer
1960 local isPerformanceStatsVisible = (GameSettings.PerformanceStatsVisible and localPlayer ~= nil)
1961 local yOffset = CHAT_WINDOW_Y_OFFSET
1962 if isPerformanceStatsVisible then
1963 yOffset = yOffset + StatsUtils.ButtonHeight
1964 end
1965 container.Position = UDim2.new(0, 0, 0, yOffset);
1966
1967 -- Account for new screen size, if applicable.
1968 if (newSize == nil) then
1969 return
1970 end
1971
1972 if InputService.VREnabled then
1973 container.Size = UDim2.new(1,0,1,0)
1974 -- Phone
1975 elseif newSize.X <= 640 then
1976 container.Size = UDim2.new(0.5,0,0.5,0) - container.Position
1977 -- Tablet
1978 elseif newSize.X <= 1024 then
1979 container.Size = UDim2.new(0.4,0,0.3,0) - container.Position
1980 -- Desktop
1981 else
1982 container.Size = UDim2.new(0.3,0,0.25,0) - container.Position
1983 end
1984 end
1985
1986 -- When quick profiler button row visiblity changes, update position of chat window.
1987 GameSettings.PerformanceStatsVisibleChanged:connect(function()
1988 if not chatRepositioned then
1989 UpdateChatWindowLayout(nil)
1990 end
1991 end)
1992
1993 GuiRoot.Changed:connect(function(prop)
1994 if (prop == "AbsoluteSize" and not chatRepositioned) then
1995 UpdateChatWindowLayout(GuiRoot.AbsoluteSize)
1996 end
1997 end)
1998
1999 UpdateChatWindowLayout()
2000
2001 messageContainer.Changed:connect(OnChatWindowResize)
2002 scrollingFrame.Changed:connect(OnChatWindowResize)
2003
2004 this.ChatContainer = container
2005 this.ScrollingFrame = scrollingFrame
2006 this.MessageContainer = messageContainer
2007 this.ChatContainer.Parent = GuiRoot
2008
2009
2010 -- It is important to set this to true in NON_CORESCRIPT_MODE because normally the topbar sets
2011 -- the chat window to visible
2012 if NON_CORESCRIPT_MODE then
2013 this:ToggleVisibility(true)
2014 end
2015
2016 --- BACKGROUND FADING CODE ---
2017 -- This is so we don't accidentally fade out when we are scrolling and mess with the scrollbar.
2018 local dontFadeOutOnMouseLeave = false
2019
2020 if Util:IsTouchDevice() then
2021 local touchCount = 0
2022 this.InputBeganConn = InputService.InputBegan:connect(function(inputObject)
2023 if inputObject.UserInputType == Enum.UserInputType.Touch and inputObject.UserInputState == Enum.UserInputState.Begin then
2024 if PointInChatWindow(Vector2.new(inputObject.Position.X, inputObject.Position.Y)) then
2025 touchCount = touchCount + 1
2026 dontFadeOutOnMouseLeave = true
2027 end
2028 end
2029 end)
2030
2031 this.InputEndedConn = InputService.InputEnded:connect(function(inputObject)
2032 if inputObject.UserInputType == Enum.UserInputType.Touch and inputObject.UserInputState == Enum.UserInputState.End then
2033 local endedCount = touchCount
2034 wait(2)
2035 if touchCount == endedCount then
2036 dontFadeOutOnMouseLeave = false
2037 end
2038 end
2039 end)
2040
2041 spawn(function()
2042 local now = tick()
2043 while true do
2044 wait()
2045 now = tick()
2046 if this.BackgroundVisible then
2047 if not dontFadeOutOnMouseLeave then
2048 this:FadeOut(0.25)
2049 end
2050 -- If background is not visible/in-focus
2051 elseif this.ChatsVisible and now > lastChatActivity + MESSAGES_FADE_OUT_TIME then
2052 this:FadeOutChats()
2053 end
2054 end
2055 end)
2056 else
2057 this.LastMousePosition = Vector2.new()
2058
2059 this.MouseEnterFrameConn = this.ChatContainer.MouseEnter:connect(function()
2060 lastEnterTime = tick()
2061 if this.BackgroundTweener and not this.BackgroundTweener:IsFinished() and not this.BackgroundVisible then
2062 this:FadeIn()
2063 end
2064 end)
2065
2066 this.MouseMoveConn = InputService.InputChanged:connect(function(inputObject)
2067 if inputObject.UserInputType == Enum.UserInputType.MouseMovement then
2068 lastMoveTime = tick()
2069 this.LastMousePosition = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
2070 if this.BackgroundTweener and this.BackgroundTweener:GetPercentComplete() < 0.5 and this.BackgroundVisible then
2071 if not dontFadeOutOnMouseLeave then
2072 this:FadeOut()
2073 end
2074 end
2075 end
2076 end)
2077
2078 local clickCount = 0
2079 this.InputBeganConn = InputService.InputBegan:connect(function(inputObject)
2080 if inputObject.UserInputType == Enum.UserInputType.MouseButton1 and inputObject.UserInputState == Enum.UserInputState.Begin then
2081 if PointInChatWindow(Vector2.new(inputObject.Position.X, inputObject.Position.Y)) then
2082 clickCount = clickCount + 1
2083 dontFadeOutOnMouseLeave = true
2084 end
2085 end
2086 end)
2087
2088 this.InputEndedConn = InputService.InputEnded:connect(function(inputObject)
2089 if inputObject.UserInputType == Enum.UserInputType.MouseButton1 and inputObject.UserInputState == Enum.UserInputState.End then
2090 local nowCount = clickCount
2091 wait(1.3)
2092 if nowCount == clickCount then
2093 dontFadeOutOnMouseLeave = false
2094 end
2095 end
2096 end)
2097
2098 this.MouseLeaveFrameConn = this.ChatContainer.MouseLeave:connect(function()
2099 lastLeaveTime = tick()
2100 if this.BackgroundTweener and not this.BackgroundTweener:IsFinished() and this.BackgroundVisible then
2101 if not dontFadeOutOnMouseLeave then
2102 this:FadeOut()
2103 end
2104 end
2105 end)
2106
2107 spawn(function()
2108 while true do
2109 wait()
2110 local now = tick()
2111 if this:IsHovering() then
2112 if now - lastMoveTime > 1.3 and not this.BackgroundVisible then
2113 this:FadeIn()
2114 end
2115 else -- not this:IsHovering()
2116 if this.BackgroundVisible then
2117 if not dontFadeOutOnMouseLeave then
2118 this:FadeOut(0.25)
2119 end
2120 -- If background is not visible/in-focus
2121 elseif this.ChatsVisible and now > lastChatActivity + MESSAGES_FADE_OUT_TIME then
2122 this:FadeOutChats()
2123 end
2124 end
2125 end
2126 end)
2127 end
2128 --- END OF BACKGROUND FADING CODE ---
2129 end
2130
2131 CreateChatWindow()
2132
2133 return this
2134end
2135
2136
2137local function CreateChat()
2138 local this = {}
2139
2140 this.Settings =
2141 {
2142 GlobalTextColor = Color3.new(112/255, 110/255, 106/255);
2143 WhisperTextColor = Color3.new(77/255, 139/255, 255/255);
2144 TeamTextColor = Color3.new(230/255, 207/255, 0);
2145 DefaultMessageTextColor = Color3.new(255/255, 255/255, 243/255);
2146 AdminTextColor = Color3.new(1, 215/255, 0);
2147 InternTextColor = Color3.new(175/255, 221/255, 1);
2148 TextStrokeTransparency = 0.75;
2149 TextStrokeColor = Color3.new(34/255,34/255,34/255);
2150 Font = Enum.Font.SourceSansBold;
2151 SmallScreenFontSize = Enum.FontSize.Size14;
2152 FontSize = Enum.FontSize.Size18;
2153 MaxWindowChatMessages = 50;
2154 MaxCharactersInMessage = 140;
2155 }
2156
2157 this.CurrentWindowMessageCountChanged = nil
2158 this.VisibilityStateChanged = Util.Signal()
2159 this.ChatBarFocusChanged = Util.Signal()
2160 this.Visible = false
2161
2162 function this:CoreGuiChanged(coreGuiType, enabled)
2163 enabled = enabled and (topbarEnabled or InputService.VREnabled)
2164 if coreGuiType == Enum.CoreGuiType.Chat or coreGuiType == Enum.CoreGuiType.All then
2165 if enabled then
2166 pcall(function()
2167 self.SpecialKeyPressedConn = Util.DisconnectEvent(self.SpecialKeyPressedConn)
2168 GuiService:AddSpecialKey(Enum.SpecialKey.ChatHotkey)
2169 self.SpecialKeyPressedConn = GuiService.SpecialKeyPressed:connect(function(key)
2170 if key == Enum.SpecialKey.ChatHotkey then
2171 if self.Visible == false then
2172 self:ToggleVisibility()
2173 end
2174 if self.ChatBarWidget then
2175 self.ChatBarWidget:FocusChatBar()
2176 end
2177 end
2178 end)
2179 end)
2180 else
2181 pcall(function() GuiService:RemoveSpecialKey(Enum.SpecialKey.ChatHotkey) end)
2182 self.SpecialKeyPressedConn = Util.DisconnectEvent(self.SpecialKeyPressedConn)
2183 end
2184 if this.MobileChatButton then
2185 if enabled == true then
2186 this.MobileChatButton.Parent = GuiRoot
2187 -- we need to set it to be visible in-case we missed a lost focus event while chat was turned off.
2188 this.MobileChatButton.Visible = true
2189 else
2190 this.MobileChatButton.Parent = nil
2191 end
2192 end
2193 end
2194 if this.ChatWindowWidget then
2195 this.ChatWindowWidget:CoreGuiChanged(coreGuiType, enabled)
2196 end
2197 if this.ChatBarWidget then
2198 this.ChatBarWidget:CoreGuiChanged(coreGuiType, enabled)
2199 end
2200 end
2201
2202 -- This event has 4 callback arguments
2203 -- Enum.PlayerChatType.{All|Team|Whisper}, chatPlayer, message, targetPlayer
2204 function this:OnPlayerChatted(playerChatType, sendingPlayer, chattedMessage, receivingPlayer)
2205 if this.ChatWindowWidget then
2206 -- Don't add messages from blocked players, don't show message if is a debug command
2207 local isDebugCommand = false
2208 pcall(function()
2209 if not NON_CORESCRIPT_MODE and sendingPlayer == PlayersService.LocalPlayer then
2210 isDebugCommand = game:GetService("GuiService"):ShowStatsBasedOnInputString(chattedMessage)
2211
2212 -- allows dev console to be opened on mobile
2213 -- NOTE: Removed ToggleDevConsole bindable event, so engine no longer handles this
2214 if string.lower(chattedMessage) == "/console" then
2215 if FFlagEnableNewDevConsole then
2216 local devConsoleMaster = require(RobloxGui.Modules.DevConsoleMaster)
2217 if devConsoleMaster then
2218 devConsoleMaster.ToggleVisibility()
2219 end
2220 else
2221 local devConsoleModule = require(RobloxGui.Modules.DeveloperConsoleModule)
2222 if devConsoleModule then
2223 local devConsoleVisible = devConsoleModule:GetVisibility()
2224 devConsoleModule:SetVisibility(not devConsoleVisible)
2225 end
2226 end
2227 elseif string.lower(chattedMessage) == "/newconsole" then
2228 local devConsoleMaster = require(RobloxGui.Modules.DevConsoleMaster)
2229 if devConsoleMaster then
2230 devConsoleMaster.ToggleVisibility()
2231 end
2232 end
2233 end
2234 end)
2235 if not (this:IsPlayerBlocked(sendingPlayer) or this:IsPlayerMuted(sendingPlayer) or isDebugCommand) then
2236 this.ChatWindowWidget:AddChatMessage(playerChatType, sendingPlayer, chattedMessage, receivingPlayer)
2237 end
2238 end
2239 end
2240
2241 function this:OnPlayerAdded(newPlayer)
2242 if newPlayer then
2243 assert(coroutine.resume(coroutine.create(function() PlayerPermissionsModule.IsPlayerAdminAsync(newPlayer) end)))
2244 end
2245 if NON_CORESCRIPT_MODE then
2246 newPlayer.Chatted:connect(function(msg, recipient)
2247 this:OnPlayerChatted(Enum.PlayerChatType.All, newPlayer, msg, recipient)
2248 end)
2249 else
2250 this.PlayerChattedConn = Util.DisconnectEvent(this.PlayerChattedConn)
2251 this.PlayerChattedConn = PlayersService.PlayerChatted:connect(function(...)
2252 this:OnPlayerChatted(...)
2253 end)
2254 end
2255 end
2256
2257 function this:IsPlayerBlocked(player)
2258 if blockingUtility then
2259 return player and blockingUtility:IsPlayerBlockedByUserId(player.UserId)
2260 else
2261 return false
2262 end
2263 end
2264
2265 function this:BlockPlayerAsync(playerToBlock)
2266 if playerToBlock and Player ~= playerToBlock then
2267 local blockUserId = playerToBlock.UserId
2268 local playerToBlockName = playerToBlock.Name
2269 if blockUserId > 0 then
2270 if not this:IsPlayerBlocked(playerToBlock) then
2271 if blockingUtility then
2272 blockingUtility:BlockPlayerAsync(playerToBlock)
2273 this.ChatWindowWidget:AddSystemChatMessage(playerToBlockName .. " is now blocked.")
2274 end
2275 else
2276 this.ChatWindowWidget:AddSystemChatMessage(playerToBlockName .. " is already blocked.")
2277 end
2278 else
2279 this.ChatWindowWidget:AddSystemChatMessage("You cannot block guests.")
2280 end
2281 else
2282 this.ChatWindowWidget:AddSystemChatMessage("You cannot block yourself.")
2283 end
2284 end
2285
2286 function this:UnblockPlayerAsync(playerToUnblock)
2287 if playerToUnblock then
2288 local unblockUserId = playerToUnblock.UserId
2289 local playerToUnblockName = playerToUnblock.Name
2290
2291 if this:IsPlayerBlocked(playerToUnblock) then
2292 if blockingUtility then
2293 this.ChatWindowWidget:AddSystemChatMessage(playerToUnblockName .. " is no longer blocked.")
2294 blockingUtility:UnblockPlayerAsync(playerToUnblock)
2295 end
2296 else
2297 this.ChatWindowWidget:AddSystemChatMessage(playerToUnblockName .. " is not blocked.")
2298 end
2299 end
2300 end
2301
2302 function this:IsPlayerMuted(player)
2303 if blockingUtility then
2304 return player and blockingUtility:IsPlayerMutedByUserId(player.UserId)
2305 else
2306 return false
2307 end
2308 end
2309
2310 function this:MutePlayer(playerToMute)
2311 if playerToMute and playerToMute ~= Player then
2312 if playerToMute.UserId > 0 then
2313 if not this:IsPlayerMuted(playerToMute) then
2314 if blockingUtility then
2315 blockingUtility:MutePlayer(playerToMute)
2316 this.ChatWindowWidget:AddSystemChatMessage(playerToMute.Name .. " is now muted.")
2317 end
2318 else
2319 this.ChatWindowWidget:AddSystemChatMessage(playerToMute.Name .. " is already muted.")
2320 end
2321 else
2322 this.ChatWindowWidget:AddSystemChatMessage("You cannot mute guests.")
2323 end
2324 else
2325 this.ChatWindowWidget:AddSystemChatMessage("You cannot mute yourself.")
2326 end
2327 end
2328
2329 function this:UnmutePlayer(playerToUnmute)
2330 if playerToUnmute then
2331 if this:IsPlayerMuted(playerToUnmute) then
2332 if blockingUtility then
2333 blockingUtility:UnmutePlayer(playerToUnmute)
2334 this.ChatWindowWidget:AddSystemChatMessage(playerToUnmute.Name .. " is no longer muted.")
2335 end
2336 else
2337 this.ChatWindowWidget:AddSystemChatMessage(playerToUnmute.Name .. " is not muted.")
2338 end
2339 end
2340 end
2341
2342 function this:CreateTouchDeviceChatButton()
2343 return Util.Create'ImageButton'
2344 {
2345 Name = 'TouchDeviceChatButton';
2346 Size = UDim2.new(0, 128, 0, 32);
2347 Position = UDim2.new(0, 88, 0, 0);
2348 BackgroundTransparency = 1.0;
2349 Image = 'https://www.roblox.com/asset/?id=97078724';
2350 };
2351 end
2352
2353 function this:PrintWelcome()
2354 if this.ChatWindowWidget then
2355 if Util.IsTouchDevice() then
2356 this.ChatWindowWidget:AddSystemChatMessage("Please press the '...' icon to chat", true)
2357 end
2358 this.ChatWindowWidget:AddSystemChatMessage("Please chat '/?' for a list of commands", true)
2359 end
2360 end
2361
2362 local doOnceVRWelcome = false
2363 function this:PrintVRWelcome()
2364 if this.ChatWindowWidget and not doOnceVRWelcome then
2365 if InputService.VREnabled then
2366 this.ChatWindowWidget:AddSystemChatMessage("Press here to chat", true)
2367 doOnceVRWelcome = true
2368 end
2369 end
2370 end
2371
2372 function this:PrintHelp()
2373 if this.ChatWindowWidget then
2374 this.ChatWindowWidget:AddSystemChatMessage("Help Menu")
2375 this.ChatWindowWidget:AddSystemChatMessage("Chat Commands:")
2376 this.ChatWindowWidget:AddSystemChatMessage("/w [PlayerName] or /whisper [PlayerName] - Whisper Chat")
2377 this.ChatWindowWidget:AddSystemChatMessage("/t or /team - Team Chat")
2378 this.ChatWindowWidget:AddSystemChatMessage("/a or /all - All Chat")
2379
2380 this.ChatWindowWidget:AddSystemChatMessage("/block [PlayerName] - Block communications from Target Player")
2381 this.ChatWindowWidget:AddSystemChatMessage("/unblock [PlayerName] - Restore communications with Target Player")
2382 this.ChatWindowWidget:AddSystemChatMessage("/mute [PlayerName] - Mute in-game communications from Target Player")
2383 this.ChatWindowWidget:AddSystemChatMessage("/unmute [PlayerName] - Restore in-game communications with Target Player")
2384 end
2385 end
2386
2387 local focusCount = 0
2388 function this:CreateGUI()
2389 if (FORCE_CHAT_GUI or
2390 (Player.ChatMode == Enum.ChatMode.TextAndMenu or RunService:IsStudio()) and
2391 game:GetService("UserInputService"):GetPlatform() ~= Enum.Platform.XBoxOne) then
2392 if NON_CORESCRIPT_MODE then
2393 local chatGui = Instance.new("ScreenGui")
2394 chatGui.Name = "RobloxGui"
2395 chatGui.Parent = Player:WaitForChild('PlayerGui')
2396 GuiRoot.Parent = chatGui
2397 end
2398
2399 -- NOTE: eventually we will make multiple chat window frames
2400 this.ChatWindowWidget = CreateChatWindowWidget(this.Settings)
2401 this.ChatBarWidget = CreateChatBarWidget(this.Settings)
2402 this.CurrentWindowMessageCountChanged = this.ChatWindowWidget.MessageCountChanged
2403
2404 this.ChatWindowWidget.FadeInSignal:connect(function()
2405 this.ChatBarWidget:FadeIn()
2406 end)
2407 this.ChatWindowWidget.FadeOutSignal:connect(function()
2408 this.ChatBarWidget:FadeOut()
2409 end)
2410
2411 this.ChatWindowWidget:FadeOut(0)
2412 this.ChatBarWidget.ChatBarGainedFocusEvent:connect(function()
2413 focusCount = focusCount + 1
2414 this.ChatWindowWidget:FadeIn(0.25)
2415 this.ChatWindowWidget:SetFadeLock(true)
2416 this.ChatBarFocusChanged:fire(true)
2417 end)
2418 this.ChatBarWidget.ChatBarLostFocusEvent:connect(function()
2419 local focusNow = focusCount
2420 if Util:IsTouchDevice() then
2421 delay(2, function()
2422 if focusNow == focusCount then
2423 this.ChatWindowWidget:SetFadeLock(false)
2424 end
2425 end)
2426 else
2427 this.ChatWindowWidget:SetFadeLock(false)
2428 end
2429 this.ChatBarFocusChanged:fire(false)
2430 end)
2431 this.ChatBarWidget.ChatBarFloodEvent:connect(function()
2432 if this.ChatWindowWidget then
2433 this.ChatWindowWidget:AddSystemChatMessage("Wait before sending another message.")
2434 end
2435 end)
2436
2437 this.ChatBarWidget.ChatErrorEvent:connect(function(msg)
2438 if msg then
2439 this.ChatWindowWidget:AddSystemChatMessage(msg)
2440 end
2441 end)
2442
2443 this.ChatBarWidget.ChatCommandEvent:connect(function(success, actionType, capture)
2444 if actionType == "Help" then
2445 this:PrintHelp()
2446 elseif actionType == "Block" then
2447 local blockPlayerName = capture and tostring(capture) or ""
2448 local playerToBlock = Util.GetPlayerByName(blockPlayerName)
2449 if playerToBlock then
2450 spawn(function() this:BlockPlayerAsync(playerToBlock) end)
2451 else
2452 this.ChatWindowWidget:AddSystemChatMessage("Cannot block " .. blockPlayerName .. " because they are not in the game.")
2453 end
2454 elseif actionType == "Unblock" then
2455 local unblockPlayerName = capture and tostring(capture) or ""
2456 local playerToBlock = Util.GetPlayerByName(unblockPlayerName)
2457 if playerToBlock then
2458 spawn(function() this:UnblockPlayerAsync(playerToBlock) end)
2459 else
2460 this.ChatWindowWidget:AddSystemChatMessage("Cannot unblock " .. unblockPlayerName .. " because they are not in the game.")
2461 end
2462 elseif actionType == "Mute" then
2463 local mutePlayerName = capture and tostring(capture) or ""
2464 local playerToMute = Util.GetPlayerByName(mutePlayerName)
2465 if playerToMute then
2466 this:MutePlayer(playerToMute)
2467 else
2468 this.ChatWindowWidget:AddSystemChatMessage("Cannot mute " .. mutePlayerName .. " because they are not in the game.")
2469 end
2470 elseif actionType == "Unmute" then
2471 local unmutePlayerName = capture and tostring(capture) or ""
2472 local playerToUnmute = Util.GetPlayerByName(unmutePlayerName)
2473 if playerToUnmute then
2474 this:UnmutePlayer(playerToUnmute)
2475 else
2476 this.ChatWindowWidget:AddSystemChatMessage("Cannot unmute " .. unmutePlayerName .. " because they are not in the game.")
2477 end
2478 elseif actionType == "Whisper" then
2479 if success == false then
2480 local playerName = capture and tostring(capture) or "Unknown"
2481 this.ChatWindowWidget:AddSystemChatMessage("Unable to Send a Whisper to Player: " .. playerName)
2482 end
2483 elseif actionType == "Unknown" then
2484 if success == false then
2485 local commandText = capture and tostring(capture) or "Unknown"
2486 this.ChatWindowWidget:AddSystemChatMessage("Invalid Slash Command: " .. commandText)
2487 end
2488 end
2489 end)
2490
2491 if not NON_CORESCRIPT_MODE then
2492 local function onVREnabled()
2493 if InputService.VREnabled then
2494 self.Settings.TextStrokeTransparency = 1
2495 self:PrintVRWelcome()
2496 local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
2497
2498 local panel = Panel3D.Get(thisModuleName)
2499 panel:LinkTo("Keyboard")
2500 panel:SetType(Panel3D.Type.Fixed)
2501 panel:ResizePixels(300, 125)
2502 GuiRoot.Parent = panel:GetGUI()
2503
2504 if this.ChatWindowWidget and this.ChatWindowWidget.ChatContainer then
2505 this.ChatWindowWidget.ChatContainer.MouseButton1Click:connect(function()
2506 if this.ChatBarWidget then
2507 if this.ChatBarWidget:WasFocused() then
2508 this.ChatBarWidget:RemoveFocus()
2509 else
2510 self:FocusChatBar()
2511 end
2512 end
2513 end)
2514 end
2515
2516 function panel:CalculateTransparency()
2517 return 0
2518 end
2519
2520 VRHub.ModuleOpened.Event:connect(function(moduleName)
2521 local module = VRHub:GetModule(moduleName)
2522 if moduleName ~= thisModuleName and module.VRIsExclusive then
2523 this:SetVisible(false)
2524 end
2525 end)
2526 else
2527 self.Settings.TextStrokeTransparency = 0.75
2528 GuiRoot.Parent = RobloxGui
2529 end
2530 end
2531 onVREnabled()
2532 InputService.Changed:connect(function(prop)
2533 if prop == 'VREnabled' then
2534 onVREnabled()
2535 end
2536 end)
2537 end
2538 end
2539 end
2540
2541 local toggleCount = 0
2542 local function SetVisbility(newVisibility)
2543 this.Visible = newVisibility
2544 if this.ChatWindowWidget then
2545 this.ChatWindowWidget:ToggleVisibility(this.Visible)
2546 if this.Visible then
2547 toggleCount = toggleCount + 1
2548 local thisToggle = toggleCount
2549 local thisFocusCount = focusCount
2550 this.ChatWindowWidget:FadeIn()
2551 this.ChatWindowWidget:SetFadeLock(true)
2552 delay(5, function()
2553 if thisToggle == toggleCount and thisFocusCount == focusCount then
2554 this.ChatWindowWidget:SetFadeLock(false)
2555 end
2556 end)
2557 end
2558 end
2559 if this.ChatBarWidget then
2560 this.ChatBarWidget:ToggleVisibility(this.Visible)
2561 if this.Visible then
2562 this.ChatBarWidget:FadeIn()
2563 end
2564 if InputService.VREnabled and not this.Visible then
2565 this.ChatBarWidget:RemoveFocus()
2566 end
2567 end
2568 if InputService.VREnabled then
2569 local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
2570
2571 local panel = Panel3D.Get(thisModuleName)
2572 if this.Visible then
2573 local topbarPanel = Panel3D.Get("Topbar3D")
2574 panel.localCF = topbarPanel.localCF * CFrame.Angles(math.rad(-5), 0, 0) * CFrame.new(0, 4, 0) * CFrame.Angles(math.rad(-15), 0, 0)
2575 panel:SetVisible(true)
2576 panel:ForceShowUntilLookedAt()
2577
2578 VRHub:FireModuleOpened(thisModuleName)
2579 else
2580 panel:SetVisible(this.Visible)
2581
2582 VRHub:FireModuleClosed(thisModuleName)
2583 end
2584 end
2585 this.VisibilityStateChanged:fire(this.Visible)
2586 end
2587
2588 function this:ToggleVisibility()
2589 SetVisbility(not self.Visible)
2590 end
2591
2592 function this:SetVisible(visible)
2593 SetVisbility(visible)
2594 end
2595
2596 function this:FocusChatBar()
2597 if self.ChatBarWidget and this.Visible then
2598 self.ChatBarWidget:FocusChatBar()
2599 end
2600 end
2601
2602 function this:IsFocused(useWasFocused)
2603 if not self.ChatBarWidget then return false end
2604 return self.ChatBarWidget:IsFocused() or (useWasFocused and self.ChatBarWidget:WasFocused())
2605 end
2606
2607 function this:GetCurrentWindowMessageCount()
2608 if this.ChatWindowWidget then
2609 return this.ChatWindowWidget:GetMessageCount()
2610 end
2611 return 0
2612 end
2613
2614 function this:TopbarEnabledChanged(enabled)
2615 topbarEnabled = enabled
2616 -- Update coregui to reflect new topbar status
2617 self:CoreGuiChanged(Enum.CoreGuiType.Chat, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
2618 end
2619
2620 function this:Initialize()
2621 --[[ Developer Customization API ]]--
2622 if not NON_CORESCRIPT_MODE then
2623 StarterGui:RegisterSetCore("ChatMakeSystemMessage", function(informationTable)
2624 if this.ChatWindowWidget then
2625 this.ChatWindowWidget:AddDeveloperSystemChatMessage(informationTable)
2626 end
2627 end)
2628 local function isUDim2Value(value)
2629 return typeof(value) == "UDim2" and value or nil
2630 end
2631
2632 local function isBubbleChatOn()
2633 return not PlayersService.ClassicChat and PlayersService.BubbleChat
2634 end
2635
2636 StarterGui:RegisterSetCore("ChatWindowPosition", function(value)
2637 if this.ChatWindowWidget and this.ChatBarWidget then
2638 value = isUDim2Value(value)
2639 if value ~= nil and not isBubbleChatOn() then
2640 chatRepositioned = true -- Prevent chat from moving back to the original position on screen resolution change
2641 this.ChatWindowWidget.ChatContainer.Position = value
2642 this.ChatBarWidget.ChatBarContainer.Position = value + UDim2.new(0, 0, this.ChatWindowWidget.ChatContainer.Size.Y.Scale, this.ChatWindowWidget.ChatContainer.Size.Y.Offset + 2)
2643 end
2644 end
2645 end)
2646
2647 StarterGui:RegisterSetCore("ChatWindowSize", function(value)
2648 if this.ChatWindowWidget and this.ChatBarWidget then
2649 value = isUDim2Value(value)
2650 if value ~= nil and not isBubbleChatOn() then
2651 chatRepositioned = true
2652 this.ChatWindowWidget.ChatContainer.Size = value
2653 this.ChatBarWidget.ChatBarContainer.Size = UDim2.new(this.ChatWindowWidget.ChatContainer.Size.X.Scale, this.ChatWindowWidget.ChatContainer.Size.X.Offset, this.ChatBarWidget.ChatBarContainer.Size.Y.Scale, this.ChatBarWidget.ChatBarContainer.Size.Y.Offset)
2654 this.ChatBarWidget.ChatBarContainer.Position = this.ChatWindowWidget.ChatContainer.Position + UDim2.new(0, 0, this.ChatWindowWidget.ChatContainer.Size.Y.Scale, this.ChatWindowWidget.ChatContainer.Size.Y.Offset + 2)
2655 end
2656 end
2657 end)
2658
2659 StarterGui:RegisterGetCore("ChatWindowPosition", function()
2660 if this.ChatWindowWidget then
2661 return this.ChatWindowWidget.ChatContainer.Position
2662 else
2663 return nil
2664 end
2665 end)
2666
2667 StarterGui:RegisterGetCore("ChatWindowSize", function()
2668 if this.ChatWindowWidget then
2669 return this.ChatWindowWidget.ChatContainer.Size
2670 else
2671 return nil
2672 end
2673 end)
2674
2675 StarterGui:RegisterSetCore("ChatBarDisabled", function(value)
2676 if this.ChatBarWidget then
2677 if type(value) == "boolean" then
2678 chatBarDisabled = value
2679 if value == true then
2680 this.ChatBarWidget:ToggleVisibility(false)
2681 end
2682 end
2683 end
2684 end)
2685
2686 StarterGui:RegisterGetCore("ChatBarDisabled", function() return chatBarDisabled end)
2687 end
2688
2689 this:OnPlayerAdded(Player)
2690 -- Upsettingly, it seems everytime a player is added, you have to redo the connection
2691 -- NOTE: PlayerAdded only fires on the server, hence ChildAdded is used here
2692 PlayersService.ChildAdded:connect(function(child)
2693 if child:IsA('Player') then
2694 this:OnPlayerAdded(child)
2695 end
2696 end)
2697 this:CreateGUI()
2698
2699
2700 this:CoreGuiChanged(Enum.CoreGuiType.Chat, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
2701 this.CoreGuiChangedConn = Util.DisconnectEvent(this.CoreGuiChangedConn)
2702 pcall(function()
2703 this.CoreGuiChangedConn = StarterGui.CoreGuiChangedSignal:connect(
2704 function(coreGuiType,enabled)
2705 this:CoreGuiChanged(coreGuiType, enabled)
2706 end)
2707 end)
2708
2709 if not NON_CORESCRIPT_MODE then
2710 this:PrintWelcome()
2711 end
2712
2713 --SetVisbility(true)
2714 end
2715
2716 return this
2717end
2718
2719local moduleApiTable = {}
2720-- Main Entry Point
2721do
2722 moduleApiTable.ModuleName = thisModuleName
2723 moduleApiTable.KeepVRTopbarOpen = true
2724 moduleApiTable.VRIsExclusive = true
2725 moduleApiTable.VRClosesNonExclusive = false
2726 VRHub:RegisterModule(moduleApiTable)
2727
2728 VRHub.ModuleOpened.Event:connect(function(moduleName)
2729 if moduleName ~= thisModuleName then
2730 local module = VRHub:GetModule(moduleName)
2731 if module.VRIsExclusive then
2732 moduleApiTable:SetVisible(false)
2733 end
2734 end
2735 end)
2736
2737 local ChatInstance = CreateChat()
2738 ChatInstance:Initialize()
2739
2740 function moduleApiTable:ToggleVisibility()
2741 ChatInstance:ToggleVisibility()
2742 end
2743
2744 function moduleApiTable:SetVisible(visible)
2745 ChatInstance:SetVisible(visible)
2746 end
2747
2748 function moduleApiTable:FocusChatBar()
2749 ChatInstance:FocusChatBar()
2750 end
2751
2752 function moduleApiTable:GetVisibility()
2753 return ChatInstance.Visible
2754 end
2755
2756 function moduleApiTable:GetMessageCount()
2757 return ChatInstance:GetCurrentWindowMessageCount()
2758 end
2759
2760 function moduleApiTable:TopbarEnabledChanged(...)
2761 return ChatInstance:TopbarEnabledChanged(...)
2762 end
2763
2764 function moduleApiTable:IsFocused(useWasFocused)
2765 return ChatInstance:IsFocused(useWasFocused)
2766 end
2767
2768 function moduleApiTable:ClassicChatEnabled()
2769 return PlayersService.ClassicChat
2770 end
2771
2772 function moduleApiTable:IsBubbleChatOnly()
2773 return PlayersService.BubbleChat and not PlayersService.ClassicChat
2774 end
2775
2776 function moduleApiTable:IsDisabled()
2777 return false
2778 end
2779
2780 moduleApiTable.ChatBarFocusChanged = ChatInstance.ChatBarFocusChanged
2781 moduleApiTable.VisibilityStateChanged = ChatInstance.VisibilityStateChanged
2782 moduleApiTable.MessagesChanged = ChatInstance.CurrentWindowMessageCountChanged
2783
2784end
2785
2786return moduleApiTable