Guest User

Untitled

a guest
Feb 1st, 2019
234
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 99.73 KB | None | 0 0
  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
  15. local FORCE_CHAT_GUI = false
  16. local 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.  
  21. local MESSAGES_FADE_OUT_TIME = 30
  22. local MAX_UDIM_SIZE = 2^15 - 1
  23.  
  24. local CHAT_WINDOW_Y_OFFSET = 2
  25.  
  26. local PHONE_SCREEN_WIDTH = 640
  27. local TABLET_SCREEN_WIDTH = 1024
  28.  
  29. local FLOOD_CHECK_MESSAGE_COUNT = 7
  30. local FLOOD_CHECK_MESSAGE_INTERVAL = 15 -- This is in seconds
  31.  
  32. local VR_CHAT_CLICK_DEBOUNCE = 0.25
  33.  
  34. local SCROLLBAR_THICKNESS = 7
  35. local 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.  
  47. local thisModuleName = "Chat"
  48.  
  49. local emptySelectionImage = Instance.new("ImageLabel")
  50. emptySelectionImage.ImageTransparency = 1
  51. emptySelectionImage.BackgroundTransparency = 1
  52.  
  53. --[[ END OF CONSTANTS ]]
  54.  
  55. --[[ SERVICES ]]
  56. local RunService = game:GetService('RunService')
  57. local CoreGuiService = game:GetService('CoreGui')
  58. local PlayersService = game:GetService('Players')
  59. local DebrisService = game:GetService('Debris')
  60. local GuiService = game:GetService('GuiService')
  61. local InputService = game:GetService('UserInputService')
  62. local StarterGui = game:GetService('StarterGui')
  63. local ContextActionService = game:GetService('ContextActionService')
  64. local Settings = UserSettings()
  65. local GameSettings = Settings.GameSettings
  66. --[[ END OF SERVICES ]]
  67.  
  68. --[[ Fast Flags ]]--
  69. local FFlagEnableNewDevConsole = settings():GetFFlag("EnableNewDevConsole")
  70.  
  71. --[[ SCRIPT VARIABLES ]]
  72. local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
  73. local VRHub = require(RobloxGui.Modules.VR.VRHub)
  74. local PlayerPermissionsModule = require(RobloxGui.Modules.PlayerPermissionsModule)
  75. local StatsUtils = require(RobloxGui.Modules.Stats.StatsUtils)
  76.  
  77. -- I am not fond of waiting at the top of the script here...
  78. while PlayersService.LocalPlayer == nil do PlayersService.ChildAdded:wait() end
  79. local Player = PlayersService.LocalPlayer
  80. -- GuiRoot will act as the top-node for parenting GUIs
  81. local GuiRoot = Instance.new('Frame')
  82. GuiRoot.Name = 'GuiRoot';
  83. GuiRoot.Size = UDim2.new(1,0,1,0);
  84. GuiRoot.BackgroundTransparency = 1;
  85.  
  86.  
  87.  
  88. local chatRepositioned = false
  89. local chatBarDisabled = false
  90.  
  91. local lastSelectedPlayer = nil
  92. local lastSelectedButton = nil
  93.  
  94. local blockingUtility = nil
  95.  
  96. local topbarEnabled = true
  97.  
  98. if not NON_CORESCRIPT_MODE and not InputService.VREnabled then
  99. blockingUtility = require(RobloxGui.Modules:WaitForChild("PlayerDropDown")):CreateBlockingUtility()
  100. end
  101.  
  102. --[[ END OF SCRIPT VARIABLES ]]
  103.  
  104. local function GetLuaChatFilteringFlag()
  105. return true
  106. end
  107.  
  108. local Util = {}
  109. do
  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
  367. end
  368.  
  369. local SelectChatModeEvent = Util.Signal()
  370. local SelectPlayerEvent = Util.Signal()
  371.  
  372. local 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
  416. end
  417.  
  418. local 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
  545. end
  546.  
  547. --[[ Popup Handling ]]--
  548. function 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
  562. end
  563.  
  564. function popupHidden()
  565. if lastSelectedButton then
  566. lastSelectedPlayer = nil
  567. lastSelectedButton.BackgroundTransparency = 1
  568. lastSelectedButton = nil
  569. end
  570. end
  571.  
  572. InputService.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.  
  583. local 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
  917. end
  918.  
  919. local 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
  1525. end
  1526.  
  1527. local 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
  2134. end
  2135.  
  2136.  
  2137. local 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
  2717. end
  2718.  
  2719. local moduleApiTable = {}
  2720. -- Main Entry Point
  2721. do
  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.  
  2784. end
  2785.  
  2786. return moduleApiTable
Advertisement
Add Comment
Please, Sign In to add comment