--game:GetService("StarterGui"):SetCoreGuiEnabled("Chat", false) local NameColorDependsOnChatColor = true local StrokeTransparency = 0.5 -- Number 0 to 1. Lower stroke transparency is easier to read against colorful backgrounds. while not _G.PlayerListAPI do wait() end local PLAYERLIST_ADMINS, PLAYERLIST_CHATCOLORS, PLAYERLIST_ALIASES = _G.PlayerListAPI:GetChatData() local forceChatGUI = false -- Utility functions + Globals local function WaitForChild(parent, childName) while parent:FindFirstChild(childName) == nil do parent.ChildAdded:wait(0.03) end return parent[childName] end local function typedef(obj) return obj end local function IsPhone() local rGui = script.Parent if rGui.AbsoluteSize.Y < 600 then return true end return false end local export = {} export.NameColorDependsOnChatColor = NameColorDependsOnChatColor export.SetNameColorDependsOnChatColor = function(self, b) self.NameColorDependsOnChatColor = b end -- Users can use enough white spaces to spoof chatting as other players -- This function removes trailing and leading white spaces -- AFAIK, there is no reason for spam white spaces local function StringTrim(str) -- %S is whitespaces -- When we find the first non space character defined by ^%s -- we yank out anything in between that and the end of the string -- Everything else is replaced with %1 which is essentially nothing return (str:gsub("^%s*(.-)%s*$", "%1")) end while Game.Players.LocalPlayer == nil do wait(0.03) end local Player = Game.Players.LocalPlayer while Player.Character == nil do wait(0.03) end local RbxUtility = LoadLibrary('RbxUtility') local Gui = typedef(RbxUtility) local Camera = Game.Workspace.CurrentCamera -- Services local CoreGuiService = Game:GetService('CoreGui') local PlayersService = Game:GetService('Players') local DebrisService= Game:GetService('Debris') local GuiService = Game:GetService('GuiService') -- Lua Enums local Enums do Enums = {} local EnumName = {} -- used as unique key for enum name local enum_mt = { __call = function(self,value) return self[value] or self[tonumber(value)] end; __index = { GetEnumItems = function(self) local t = {} for i,item in pairs(self) do if type(i) == 'number' then t[#t+1] = item end end table.sort(t,function(a,b) return a.Value < b.Value end) return t end; }; __tostring = function(self) return "Enum." .. self[EnumName] end; } local item_mt = { __call = function(self,value) return value == self or value == self.Name or value == self.Value end; __tostring = function(self) return "Enum." .. self[EnumName] .. "." .. self.Name end; } function CreateEnum(enumName) return function(t) local e = {[EnumName] = enumName} for i,name in pairs(t) do local item = setmetatable({Name=name,Value=i,Enum=e,[EnumName]=enumName},item_mt) e[i] = item e[name] = item e[item] = item end Enums[enumName] = e return setmetatable(e, enum_mt) end end end --------------------------------------------------- ------------------ Input class -------------------- local Input = { Mouse = Player:GetMouse(), Speed = 0, Simulating = false, Configuration = { DefaultSpeed = 1 }, UserIsScrolling = false } --------------------------------------------------- ------------------ Chat class -------------------- local Chat = { ChatColors = { BrickColor.new("Bright red"), BrickColor.new("Bright blue"), BrickColor.new("Earth green"), BrickColor.new("Bright violet"), BrickColor.new("Bright orange"), BrickColor.new("Bright yellow"), BrickColor.new("Light reddish violet"), BrickColor.new("Brick yellow"), }, Gui = nil, Frame = nil, RenderFrame = nil, TapToChatLabel = nil, ClickToChatButton = nil, ScrollingLock = false, EventListener = nil, -- This is actually a ring buffer -- Meaning at hitting the historyLength it wraps around -- Reuses the text objects, so chat atmost uses 100 text objects MessageQueue = {}, -- Stores all the values for configuring chat Configuration = { FontSize = Enum.FontSize.Size12, -- 10 is good -- Also change this when you are changing the above, this is suboptimal but so is our interface to find FontSize NumFontSize = 12, HistoryLength = 20, -- stores up to 50 of the last chat messages for you to scroll through, Size = UDim2.new(0.38, 0, 0.20, 0), MessageColor = Color3.new(1, 1, 1), AdminMessageColor = Color3.new(1, 215/255, 0), XScale = 0.025, LifeTime = 45, Position = UDim2.new(0, 2, 0.05, 0), DefaultTweenSpeed = 0.15, }, -- This could be redone by just using the previous and next fields of the Queue -- But the iterators cause issues, will be optimized later SlotPositions_List = {}, -- To precompute and store all player null strings since its an expensive process CachedSpaceStrings_List = {}, MouseOnFrame = false, GotFocus = false, Messages_List = {}, MessageThread = nil, Admins_List = PLAYERLIST_ADMINS, SafeChat_List = { ['Use the Chat menu to talk to me.'] = {'/sc 0', true}, ['I can only see menu chats.'] = {'/sc 1', true}, ['Hello'] = { ['Hi'] = {'/sc 2_0', true, ['Hi there!'] = true, ['Hi everyone'] = true}, ['Howdy'] = {'/sc 2_1', true, ['Howdy partner!'] = true}, ['Greetings'] = {'/sc 2_2', true, ['Greetings everyone'] = true, ['Greetings Robloxians!'] = true, ['Seasons greetings!'] = true}, ['Welcome'] = {'/sc 2_3', true, ['Welcome to my place'] = true, ['Welcome to my barbeque'] = true, ['Welcome to our base'] = true}, ['Hey there!'] = {'/sc 2_4', true}, ['What\'s up?'] = {'/sc 2_5', true, ['How are you doing?'] = true, ['How\'s it going?'] = true, ['What\'s new?'] = true}, ['Good day'] = {'/sc 2_6', true, ['Good morning'] = true, ['Good evening'] = true, ['Good afternoon'] = true, ['Good night'] = true}, ['Silly'] = {'/sc 2_7', true, ['Waaaaaaaz up?!'] = true, ['Hullo!'] = true, ['Behold greatness, mortals!'] = true, ['Pardon me, is this Sparta?'] = true, ['THIS IS SPARTAAAA!'] = true}, ['Happy Holidays!'] = {'/sc 2_8', true, ['Happy New Year!'] = true, ['Happy Valentine\'s Day!'] = true, ['Beware the Ides of March!'] = true, ['Happy St. Patrick\'s Day!'] = true, ['Happy Easter!'] = true, ['Happy Earth Day!'] = true, ['Happy 4th of July!'] = true, ['Happy Thanksgiving!'] = true, ['Happy Halloween!'] = true, ['Happy Hanukkah!'] = true, ['Merry Christmas!'] = true, ['Happy Halloween!'] = true, ['Happy Earth Day!'] = true, ['Happy May Day!'] = true, ['Happy Towel Day!'] = true, ['Happy ROBLOX Day!'] = true, ['Happy LOL Day!'] = true }, [1] = '/sc 2' }, ['Goodbye'] = { ['Good Night']= {'/sc 3_0', true, ['Sweet dreams'] = true, ['Go to sleep!'] = true, ['Lights out!'] = true, ['Bedtime'] = true, ['Going to bed now'] = true}, ['Later']= {'/sc 3_1', true, ['See ya later'] = true, ['Later gator!'] = true, ['See you tomorrow'] = true}, ['Bye'] = {'/sc 3_2', true, ['Hasta la bye bye!'] = true}, ['I\'ll be right back'] = {'/sc 3_3', true}, ['I have to go'] = {'/sc 3_4', true}, ['Farewell'] = {'/sc 3_5', true, ['Take care'] = true, ['Have a nice day'] = true, ['Goodluck!'] = true, ['Ta-ta for now!'] = true}, ['Peace'] = {'/sc 3_6', true, ['Peace out!'] = true, ['Peace dudes!'] = true, ['Rest in pieces!'] = true}, ['Silly'] = {'/sc 3_7', true, ['To the batcave!'] = true, ['Over and out!'] = true, ['Happy trails!'] = true, ['I\'ve got to book it!'] = true, ['Tootles!'] = true, ['Smell you later!'] = true, ['GG!'] = true, ['My house is on fire! gtg.'] = true}, [1] = '/sc 3' }, ['Friend'] ={ ['Wanna be friends?'] = {'/sc 4_0', true}, ['Follow me'] = {'/sc 4_1', true, ['Come to my place!'] = true, ['Come to my base!'] = true, ['Follow me, team!'] = true, ['Follow me'] = true}, ['Your place is cool'] = {'/sc 4_2', true, ['Your place is fun'] = true, ['Your place is awesome'] = true, ['Your place looks good'] = true, ['This place is awesome!'] = true}, ['Thank you'] = {'/sc 4_3', true, ['Thanks for playing'] = true, ['Thanks for visiting'] = true, ['Thanks for everything'] = true, ['No, thank you'] = true, ['Thanx'] = true}, ['No problem'] = {'/sc 4_4', true, ['Don\'t worry'] = true, ['That\'s ok'] = true, ['np'] = true}, ['You are ...'] = {'/sc 4_5', true, ['You are great!'] = true, ['You are good!'] = true, ['You are cool!'] = true, ['You are funny!'] = true, ['You are silly!'] = true, ['You are awesome!'] = true, ['You are doing something I don\'t like, please stop'] = true }, ['I like ...'] = {'/sc 4_6', true, ['I like your name'] = true, ['I like your shirt'] = true, ['I like your place'] = true, ['I like your style'] = true, ['I like you'] = true, ['I like items'] = true, ['I like money'] = true}, ['Sorry'] = {'/sc 4_7', true, ['My bad!'] = true, ['I\'m sorry'] = true, ['Whoops!'] = true, ['Please forgive me.'] = true, ['I forgive you.'] = true, ['I didn\'t mean to do that.'] = true, ['Sorry, I\'ll stop now.'] = true}, [1] = '/sc 4' }, ['Questions'] = { ['Who?'] = {'/sc 5_0', true, ['Who wants to be my friend?'] = true, ['Who wants to be on my team?'] = true, ['Who made this brilliant game?'] = true}, ['What?'] = {'/sc 5_1', true, ['What is your favorite animal?'] = true, ['What is your favorite game?'] = true, ['What is your favorite movie?'] = true, ['What is your favorite TV show?'] = true, ['What is your favorite music?'] = true, ['What are your hobbies?'] = true, ['LOLWUT?'] = true}, ['When?'] = {'/sc 5_2', true, ['When are you online?'] = true, ['When is the new version coming out?'] = true, ['When can we play again?'] = true, ['When will your place be done?'] = true}, ['Where?'] = {'/sc 5_3', true, ['Where do you want to go?'] = true, ['Where are you going?'] = true, ['Where am I?!'] = true, ['Where did you go?'] = true}, ['How?'] = {'/sc 5_4', true, ['How are you today?'] = true, ['How did you make this cool place?'] = true, ['LOLHOW?'] = true}, ['Can I...'] = {'/sc 5_5', true, ['Can I have a tour?'] = true, ['Can I be on your team?'] = true, ['Can I be your friend?'] = true, ['Can I try something?'] = true, ['Can I have that please?'] = true, ['Can I have that back please?'] = true, ['Can I have borrow your hat?'] = true, ['Can I have borrow your gear?'] = true}, [1] = '/sc 5' }, ['Answers'] = { ['You need help?'] = {'/sc 6_0', true, ['Check out the news section'] = true, ['Check out the help section'] = true, ['Read the wiki!'] = true, ['All the answers are in the wiki!'] = true, ['I will help you with this.'] = true}, ['Some people ...'] = {'/sc 6_1', true, ['Me'] = true, ['Not me'] = true, ['You'] = true, ['All of us'] = true, ['Everyone but you'] = true, ['Builderman!'] = true, ['Telamon!'] = true, ['My team'] = true, ['My group'] = true, ['Mom'] = true, ['Dad'] = true, ['Sister'] = true, ['Brother'] = true, ['Cousin'] = true, ['Grandparent'] = true, ['Friend'] = true}, ['Time ...'] = {'/sc 6_2', true, ['In the morning'] = true, ['In the afternoon'] = true, ['At night'] = true, ['Tomorrow'] = true, ['This week'] = true, ['This month'] = true, ['Sometime'] = true, ['Sometimes'] = true, ['Whenever you want'] = true, ['Never'] = true, ['After this'] = true, ['In 10 minutes'] = true, ['In a couple hours'] = true, ['In a couple days'] = true}, ['Animals'] = {'/sc 6_3', true, ['Cats'] = {['Lion'] = true, ['Tiger'] = true, ['Leopard'] = true, ['Cheetah'] = true}, ['Dogs'] = {['Wolves'] = true, ['Beagle'] = true, ['Collie'] = true, ['Dalmatian'] = true, ['Poodle'] = true, ['Spaniel'] = true, ['Shepherd'] = true, ['Terrier'] = true, ['Retriever'] = true}, ['Horses'] = {['Ponies'] = true, ['Stallions'] = true, ['Pwnyz'] = true}, ['Reptiles'] = {['Dinosaurs'] = true, ['Lizards'] = true, ['Snakes'] = true, ['Turtles!'] = true}, ['Hamster'] = true, ['Monkey'] = true, ['Bears'] = true, ['Fish'] = {['Goldfish'] = true, ['Sharks'] = true, ['Sea Bass'] = true, ['Halibut'] = true, ['Tropical Fish'] = true}, ['Birds'] = {['Eagles'] = true, ['Penguins'] = true, ['Parakeets'] = true, ['Owls'] = true, ['Hawks'] = true, ['Pidgeons'] = true}, ['Elephants'] = true, ['Mythical Beasts'] = {['Dragons'] = true, ['Unicorns'] = true, ['Sea Serpents'] = true, ['Sphinx'] = true, ['Cyclops'] = true, ['Minotaurs'] = true, ['Goblins'] = true, ['Honest Politicians'] = true, ['Ghosts'] = true, ['Scylla and Charybdis'] = true} }, ['Games'] = {'/sc 6_4', true, ['Action'] = true, ['Puzzle'] = true, ['Strategy'] = true, ['Racing'] = true, ['RPG'] = true, ['Obstacle Course'] = true, ['Tycoon'] = true, ['Roblox'] = { ['BrickBattle'] = true, ['Community Building'] = true, ['Roblox Minigames'] = true, ['Contest Place'] = true}, ['Board games'] = { ['Chess'] = true, ['Checkers'] = true, ['Settlers of Catan'] = true, ['Tigris and Euphrates'] = true, ['El Grande'] = true, ['Stratego'] = true, ['Carcassonne'] = true} }, ['Sports'] = {'/sc 6_5', true, ['Hockey'] = true, ['Soccer'] = true, ['Football'] = true, ['Baseball'] = true, ['Basketball'] = true, ['Volleyball'] = true, ['Tennis'] = true, ['Sports team practice'] = true, ['Watersports'] = { ['Surfing'] = true,['Swimming'] = true, ['Water Polo'] = true}, ['Winter sports'] = { ['Skiing'] = true, ['Snowboarding'] = true, ['Sledding'] = true, ['Skating'] = true}, ['Adventure'] = {['Rock climbing'] = true, ['Hiking'] = true, ['Fishing'] = true, ['Horseback riding'] = true}, ['Wacky'] = {['Foosball'] = true, ['Calvinball'] = true, ['Croquet'] = true, ['Cricket'] = true, ['Dodgeball'] = true, ['Squash'] = true, ['Trampoline'] = true} }, ['Movies/TV'] = {'/sc 6_6', true, ['Science Fiction'] = true, ['Animated'] = {['Anime'] = true}, ['Comedy'] = true, ['Romantic'] = true, ['Action'] = true, ['Fantasy'] = true}, ['Music'] = {'/sc 6_7', true, ['Country'] = true, ['Jazz'] = true, ['Rap'] = true, ['Hip-hop'] = true, ['Techno'] = true, ['Classical'] = true, ['Pop'] = true, ['Rock'] = true}, ['Hobbies'] = {'/sc 6_8', true, ['Computers'] = { ['Building computers'] = true, ['Videogames'] = true, ['Coding'] = true, ['Hacking'] = true}, ['The Internet'] = { ['lol. teh internets!'] = true, ['Watching vids'] = true}, ['Dance'] = true, ['Gymnastics'] = true, ['Listening to music'] = true, ['Arts and crafts'] = true, ['Martial Arts'] = {['Karate'] = true, ['Judo'] = true, ['Taikwon Do'] = true, ['Wushu'] = true, ['Street fighting'] = true}, ['Music lessons'] = {['Playing in my band'] = true, ['Playing piano'] = true, ['Playing guitar'] = true, ['Playing violin'] = true, ['Playing drums'] = true, ['Playing a weird instrument'] = true} }, ['Location'] = {'/sc 6_9', true, ['USA'] = { ['West'] = { ['Alaska'] = true, ['Arizona'] = true, ['California'] = true, ['Colorado'] = true, ['Hawaii'] = true, ['Idaho'] = true, ['Montana'] = true, ['Nevada'] = true, ['New Mexico'] = true, ['Oregon'] = true, ['Utah'] = true, ['Washington'] = true, ['Wyoming'] = true }, ['South'] = { ['Alabama'] = true, ['Arkansas'] = true, ['Florida'] = true, ['Georgia'] = true, ['Kentucky'] = true, ['Louisiana'] = true, ['Mississippi'] = true, ['North Carolina'] = true, ['Oklahoma'] = true, ['South Carolina'] = true, ['Tennessee'] = true, ['Texas'] = true, ['Virginia'] = true, ['West Virginia'] = true }, ['Northeast'] = {['Connecticut'] = true, ['Delaware'] = true, ['Maine'] = true, ['Maryland'] = true, ['Massachusetts'] = true, ['New Hampshire'] = true, ['New Jersey'] = true, ['New York'] = true, ['Pennsylvania'] = true, ['Rhode Island'] = true, ['Vermont'] = true }, ['Midwest'] = {['Illinois'] = true, ['Indiana'] = true, ['Iowa'] = true, ['Kansas'] = true, ['Michigan'] = true, ['Minnesota'] = true, ['Missouri'] = true, ['Nebraska'] = true, ['North Dakota'] = true, ['Ohio'] = true, ['South Dakota'] = true, ['Wisconsin'] = true} }, ['Canada'] = {['Alberta'] = true, ['British Columbia'] = true, ['Manitoba'] = true, ['New Brunswick'] = true, ['Newfoundland'] = true, ['Northwest Territories'] = true, ['Nova Scotia'] = true, ['Nunavut'] = true, ['Ontario'] = true, ['Prince Edward Island'] = true, ['Quebec'] = true, ['Saskatchewan'] = true, ['Yukon'] = true}, ['Mexico'] = true, ['Central America'] = true, ['Europe'] = {['France'] = true, ['Germany'] = true, ['Spain'] = true, ['Italy'] = true, ['Poland'] = true, ['Switzerland'] = true, ['Greece'] = true, ['Romania'] = true, ['Netherlands'] = true, ['Great Britain'] = {['England'] = true, ['Scotland'] = true, ['Wales'] = true, ['Northern Ireland'] = true} }, ['Asia'] = { ['China'] = true, ['India'] = true, ['Japan'] = true, ['Korea'] = true, ['Russia'] = true, ['Vietnam'] = true}, ['South America'] = { ['Argentina'] = true, ['Brazil'] = true}, ['Africa'] = { ['Eygpt'] = true, ['Swaziland'] = true}, ['Australia'] = true, ['Middle East'] = true, ['Antarctica'] = true, ['New Zealand'] = true }, ['Age'] = {'/sc 6_10', true, ['Rugrat'] = true, ['Kid'] = true, ['Tween'] = true, ['Teen'] = true, ['Twenties'] = true, ['Old'] = true, ['Ancient'] = true, ['Mesozoic'] = true, ['I don\'t want to say my age. Don\'t ask.'] = true}, ['Mood'] = {'/sc 6_11', true, ['Good'] = true, ['Great!'] = true, ['Not bad'] = true, ['Sad'] = true, ['Hyper'] = true, ['Chill'] = true, ['Happy'] = true, ['Kind of mad'] = true}, ['Boy'] = {'/sc 6_12', true}, ['Girl'] = {'/sc 6_13', true}, ['I don\'t want to say boy or girl. Don\'t ask.'] = {'/sc 6_14', true}, [1] = '/sc 6' }, ['Game'] = { ['Let\'s build'] = {'/sc 7_0', true}, ['Let\'s battle'] = {'/sc 7_1', true}, ['Nice one!'] = {'/sc 7_2', true}, ['So far so good'] = {'/sc 7_3', true}, ['Lucky shot!'] = {'/sc 7_4', true}, ['Oh man!'] = {'/sc 7_5', true}, ['I challenge you to a fight!'] = {'/sc 7_6', true}, ['Help me with this'] = {'/sc 7_7', true}, ['Let\'s go to your game'] = {'/sc 7_8', true}, ['Can you show me how do to that?'] = {'/sc 7_9', true}, ['Backflip!'] = {'/sc 7_10', true}, ['Frontflip!'] = {'/sc 7_11', true}, ['Dance!'] = {'/sc 7_12', true}, ['I\'m on your side!'] = {'/sc 7_13', true}, ['Game Commands'] = {'/sc 7_14', true, ['regen'] = true, ['reset'] = true, ['go'] = true, ['fix'] = true, ['respawn'] = true}, [1] = '/sc 7' }; ['Silly'] = { ['Muahahahaha!'] = true, ['all your base are belong to me!'] = true, ['GET OFF MAH LAWN'] = true, ['TEH EPIK DUCK IS COMING!!!'] = true, ['ROFL'] = true, ['1337'] = {true, ['i r teh pwnz0r!'] = true, ['w00t!'] = true, ['z0mg h4x!'] = true, ['ub3rR0xXorzage!'] = true} }, ['Yes'] = { ['Absolutely!'] = true, ['Rock on!'] = true, ['Totally!'] = true, ['Juice!'] = true, ['Yay!'] = true, ['Yesh'] = true }, ['No'] = { ['Ummm. No.'] = true, ['...'] = true, ['Stop!'] = true, ['Go away!'] = true, ['Don\'t do that'] = true, ['Stop breaking the rules'] = true, ['I don\'t want to'] = true }, ['Ok'] = { ['Well... ok'] = true, ['Sure'] = true }, ['Uncertain'] = { ['Maybe'] = true, ['I don\'t know'] = true, ['idk'] = true, ['I can\'t decide'] = true, ['Hmm...'] = true }, [':-)'] = { [':-('] = true, [':D'] = true, [':-O'] = true, ['lol'] = true, ['=D'] = true, ['D='] = true, ['XD'] = true, [';D'] = true, [';)'] = true, ['O_O'] = true, ['=)'] = true, ['@_@'] = true, ['>_<'] = true, ['T_T'] = true, ['^_^'] = true, ['<(0_0<) <(0_0)> (>0_0)> KIRBY DANCE'] = true, [')\';'] = true, [':3'] = true }, ['Ratings'] = { ['Rate it!'] = true, ['I give it a 1 out of 10'] = true, ['I give it a 2 out of 10'] = true, ['I give it a 3 out of 10'] = true, ['I give it a 4 out of 10'] = true, ['I give it a 5 out of 10'] = true, ['I give it a 6 out of 10'] = true, ['I give it a 7 out of 10'] = true, ['I give it a 8 out of 10'] = true, ['I give it a 9 out of 10'] = true, ['I give it a 10 out of 10!'] = true, } }, CreateEnum('SafeChat'){'Level1', 'Level2', 'Level3'}, SafeChatTree = {}, TempSpaceLabel = nil } --------------------------------------------------- local function GetNameValue(pName) local value = 0 for index = 1, #pName do local cValue = string.byte(string.sub(pName, index, index)) local reverseIndex = #pName - index + 1 if #pName%2 == 1 then reverseIndex = reverseIndex - 1 end if reverseIndex%4 >= 2 then cValue = -cValue end value = value + cValue end return value%8 end function Chat:ComputeChatColor(pName) if export.NameColorDependsOnChatColor then return PLAYERLIST_CHATCOLORS[pName:lower()] or self.ChatColors[GetNameValue(pName) + 1].Color else return self.ChatColors[GetNameValue(pName) + 1].Color end end -- This is context based scrolling function Chat:EnableScrolling(toggle) -- Genius idea gone to fail, if we switch the camera type we can effectively lock the -- camera and do no click scrolling self.MouseOnFrame = false if self.RenderFrame then self.RenderFrame.MouseEnter:connect(function() local character = Player.Character local torso = WaitForChild(character, 'Torso') local humanoid = WaitForChild(character, 'Humanoid') local head = WaitForChild(character, 'Head') if toggle then self.MouseOnFrame = true Camera.CameraType = 'Scriptable' -- Get relative position of camera and keep to it Spawn(function() local currentRelativePos = Camera.CoordinateFrame.p - torso.Position while Chat.MouseOnFrame do Camera.CoordinateFrame = CFrame.new(torso.Position + currentRelativePos, head.Position) wait(0.015) end end) end end) self.RenderFrame.MouseLeave:connect(function() Camera.CameraType = 'Custom' self.MouseOnFrame = false end) end end -- TODO: Scrolling using Mouse wheel function Chat:OnScroll(speed) if self.MouseOnFrame then -- end end -- Check if we are running on a touch device function Chat:IsTouchDevice() local touchEnabled = false pcall(function() touchEnabled = Game:GetService('UserInputService').TouchEnabled end) return touchEnabled end -- Scrolling function Chat:ScrollQueue(value) --[[for i = 1, #self.MessageQueue do if self.MessageQueue[i] then for _, label in pairs(self.MessageQueue[i]) do local next = self.MessageQueue[i].Next local previous = self.MessageQueue[i].Previous if label and label:IsA('TextLabel') or label:IsA('TextButton') then if value > 0 and previous and previous['Message'] then label.Position = previous['Message'].Position elseif value < 1 and next['Message'] then label.Position = previous['Message'].Position end end end end end ]] end -- Handles the rendering of the text objects in their appropriate places function Chat:UpdateQueue(field, diff) -- Have to do some sort of correction here for i = #self.MessageQueue, 1, -1 do if self.MessageQueue[i] then for _, label in pairs(self.MessageQueue[i]) do if label and type(label) ~= 'table' and type(label) ~= 'number' then if label:IsA('TextLabel') or label:IsA('TextButton') then if diff then label.Position = label.Position - UDim2.new(0, 0, diff, 0) else if field == self.MessageQueue[i] then label.Position = UDim2.new(self.Configuration.XScale, 0, label.Position.Y.Scale - field['Message'].Size.Y.Scale , 0) -- Just to show up popping effect for the latest message in chat coroutine.resume(coroutine.create(function() wait(0.05) local t = 0.1 local start = tick() while tick() - start < t do local alpha = (tick() - start) / t label.TextTransparency = 1-alpha wait() end label.TextTransparency = 0 if label == field['Message'] then label.TextStrokeTransparency = StrokeTransparency else label.TextStrokeTransparency = StrokeTransparency end end)) else label.Position = UDim2.new(self.Configuration.XScale, 0, label.Position.Y.Scale - field['Message'].Size.Y.Scale, 0) end if label.Position.Y.Scale < -0.01 then -- NOTE: Remove this fix when Textbounds is fixed label.Visible = false label:Destroy() end end end end end end end end function Chat:CreateScrollBar() -- Code for scrolling is in here, partially, but scroll bar drawing isn't drawn -- TODO: Implement end -- For scrolling, to see if we hit the bounds so that we can stop it from scrolling anymore function Chat:CheckIfInBounds(value) if #Chat.MessageQueue < 3 then return true end if value > 0 and Chat.MessageQueue[1] and Chat.MessageQueue[1]['Player'] and Chat.MessageQueue[1]['Player'].Position.Y.Scale == 0 then return true elseif value < 0 and Chat.MessageQueue[1] and Chat.MessageQueue[1]['Player'] and Chat.MessageQueue[1]['Player'].Position.Y.Scale < 0 then return true else return false end return false end -- This is to precompute all playerName space strings -- This is used to offset the message by exactly this + 2 spacestrings function Chat:ComputeSpaceString(pLabel) local nString = " " if not self.TempSpaceLabel then self.TempSpaceLabel = Gui.Create'TextButton' { Size = UDim2.new(0, pLabel.AbsoluteSize.X, 0, pLabel.AbsoluteSize.Y); FontSize = self.Configuration.FontSize; Parent = self.RenderFrame; BackgroundTransparency = 1.0; Text = nString; Name = 'SpaceButton' }; else self.TempSpaceLabel.Text = nString end while self.TempSpaceLabel.TextBounds.X < pLabel.TextBounds.X do nString = nString .. " " self.TempSpaceLabel.Text = nString end nString = nString .. " " self.CachedSpaceStrings_List[pLabel.Text] = nString self.TempSpaceLabel.Text = "" return nString end -- When the playerChatted event fires -- The message is what the player chatted function Chat:UpdateChat(cPlayer, message) local messageField = { ['Player'] = cPlayer, ['Message'] = message } if Chat.MessageThread == nil or coroutine.status(Chat.MessageThread) == 'dead' then --Chat.Messages_List = {} table.insert(Chat.Messages_List, messageField) Chat.MessageThread = coroutine.create(function() for i = 1, #Chat.Messages_List do local field = Chat.Messages_List[i] Chat:CreateMessage(field['Player'], field['Message']) end Chat.Messages_List = {} end) coroutine.resume(Chat.MessageThread) else table.insert(Chat.Messages_List, messageField) end end function Chat:RecalculateSpacing() --[[for i = 1, #self.MessageQueue do local pLabel = self.MessageQueue[i]['Player'] local mLabel = self.MessageQueue[i]['Message'] local prevYScale = mLabel.Size.Y.Scale local prevText = mLabel.Text mLabel.Text = prevText local heightField = mLabel.TextBounds.Y mLabel.Size = UDim2.new(1, 0, heightField/self.RenderFrame.AbsoluteSize.Y, 0) pLabel.Size = mLabel.Size local diff = mLabel.Size.Y.Scale - prevYScale Chat:UpdateQueue(self.MessageQueue[i], diff) end ]] end function Chat:ApplyFilter(str) --[[for _, word in pair(self.Filter_List) do if string.find(str, word) then str:gsub(word, '@#$^') end end ]] end export.SendChat = function(self, name, message, custom_color, do_not_add_to_history) local old_color = PLAYERLIST_CHATCOLORS[name:lower()] if custom_color ~= nil then PLAYERLIST_CHATCOLORS[name:lower()] = custom_color end print(old_color, PLAYERLIST_CHATCOLORS[name:lower()]) local player = game.Players:FindFirstChild(name) or { Name = name, Neutral = true, TeamColor = Color3.new(1,1,1) } Chat:CreateMessage(player, message, do_not_add_to_history) -- if do_not_add_to_history ~= true then -- AddToChatHistory(player, message) -- end wait() PLAYERLIST_CHATCOLORS[name:lower()] = old_color or nil end -- NOTE: Temporarily disabled ring buffer to allow for chat to always wrap around function Chat:CreateMessage(cPlayer, message, do_not_add_to_history) if do_not_add_to_history ~= true then AddToChatHistory(cPlayer, message) end local pName if not cPlayer then pName = '' else pName = cPlayer.Name end message = StringTrim(message) local pLabel local mLabel -- Our history stores upto 50 messages that is 100 textlabels -- If we ever hit the mark, which would be in every popular game btw -- we wrap around and reuse the labels if #self.MessageQueue > self.Configuration.HistoryLength then --[[pLabel = self.MessageQueue[#self.MessageQueue]['Player'] mLabel = self.MessageQueue[#self.MessageQueue]['Message'] pLabel.Text = pName .. ':' pLabel.Name = pName local pColor if cPlayer.Neutral then pLabel.TextColor3 = Chat:ComputeChatColor(pName) else pLabel.TextColor3 = cPlayer.TeamColor.Color end local nString if not self.CachedSpaceStrings_List[pName] then nString = Chat:ComputeSpaceString(pLabel) else nString = self.CachedSpaceStrings_List[pName] end mLabel.Text = "" mLabel.Name = pName .. " - message" mLabel.Text = nString .. message; mLabel.Parent = nil mLabel.Parent = self.RenderFrame mLabel.Position = UDim2.new(0, 0, 1, 0); pLabel.Position = UDim2.new(0, 0, 1, 0);]] -- Reinserted at the beginning, ring buffer self.MessageQueue[#self.MessageQueue] = nil end --else -- Haven't hit the mark yet, so keep creating pLabel = Gui.Create'TextLabel' { Name = pName; Text = (PLAYERLIST_ALIASES[pName:lower()] or pName) .. ":"; TextColor3 = PLAYERLIST_CHATCOLORS[pName:lower()] or pColor; FontSize = Chat.Configuration.FontSize; TextXAlignment = Enum.TextXAlignment.Left; TextYAlignment = Enum.TextYAlignment.Top; Parent = self.RenderFrame; TextWrapped = false; Size = UDim2.new(1, 0, 0.1, 0); BackgroundTransparency = 1.0; TextTransparency = 1.0; Position = UDim2.new(0, 0, 1, 0); BorderSizePixel = 0.0; TextStrokeColor3 = Color3.new(0, 0, 0); TextStrokeTransparency = StrokeTransparency; --Active = false; }; local pColor if cPlayer.Neutral then pLabel.TextColor3 = Chat:ComputeChatColor(pName) else pLabel.TextColor3 = cPlayer.TeamColor.Color end local nString if not self.CachedSpaceStrings_List[pName] then nString = Chat:ComputeSpaceString(pLabel) else nString = self.CachedSpaceStrings_List[pName] end mLabel = Gui.Create'TextLabel' { Name = pName .. ' - message'; -- Max is 3 lines Size = UDim2.new(1, 0, 0.5, 0); TextColor3 = Chat.Configuration.MessageColor; FontSize = Chat.Configuration.FontSize; TextXAlignment = Enum.TextXAlignment.Left; TextYAlignment = Enum.TextYAlignment.Top; Text = ""; -- this is to stop when the engine reverts the swear words to default, which is button, ugh Parent = self.RenderFrame; TextWrapped = true; BackgroundTransparency = 1.0; TextTransparency = 1.0; Position = UDim2.new(0, 0, 1, 0); BorderSizePixel = 0.0; TextStrokeColor3 = Color3.new(0, 0, 0); --TextStrokeTransparency = 0.8; --Active = false; }; mLabel.Text = nString .. message; if not pName then pLabel.Text = '' mLabel.TextColor3 = Color3.new(0, 0.4, 1.0) end --end if self.Admins_List[pName:lower()] then mLabel.TextColor3 = PLAYERLIST_CHATCOLORS[pName:lower()] or self.Configuration.AdminMessageColor end pLabel.Visible = true mLabel.Visible = true -- This will give beautiful multilines as well local heightField = mLabel.TextBounds.Y mLabel.Size = UDim2.new(1, 0, heightField/self.RenderFrame.AbsoluteSize.Y, 0) pLabel.Size = mLabel.Size local yPixels = self.RenderFrame.AbsoluteSize.Y local yFieldSize = mLabel.TextBounds.Y local queueField = {} queueField['Player'] = pLabel queueField['Message'] = mLabel queueField['SpawnTime'] = tick() -- Used for identifying when to make the message invisible table.insert(self.MessageQueue, 1, queueField) Chat:UpdateQueue(queueField) end function Chat:ScreenSizeChanged() wait() while self.Frame.AbsoluteSize.Y > 120 do self.Frame.Size = self.Frame.Size - UDim2.new(0, 0, 0.005, 0) end Chat:RecalculateSpacing() end function Chat:FindButtonTree(scButton, rootList) local list = {} rootList = rootList or self.SafeChatTree for button, _ in pairs(rootList) do if button == scButton then list = rootList[button] elseif type(rootList[button]) == 'table' then list = Chat:FindButtonTree(scButton, rootList[button]) end end return list end function Chat:ToggleSafeChatMenu(scButton) local list = Chat:FindButtonTree(scButton, self.SafeChatTree) if list then for button, _ in pairs(list) do if button:IsA('TextButton') or button:IsA('ImageButton') then button.Visible = not button.Visible end end return true end return false end function Chat:CreateSafeChatOptions(list, rootButton) local text_List = {} level = level or 0 local count = 0 text_List[rootButton] = {} text_List[rootButton][1] = list[1] rootButton = rootButton or self.SafeChatButton for msg, _ in pairs(list) do if type(msg) == 'string' then local chatText = Gui.Create'TextButton' { Name = msg; Text = msg; Size = UDim2.new(0, 100, 0, 20); TextXAlignment = Enum.TextXAlignment.Center; TextColor3 = Color3.new(0.2, 0.1, 0.1); BackgroundTransparency = 0.5; BackgroundColor3 = Color3.new(1, 1, 1); Parent = self.SafeChatFrame; Visible = false; Position = UDim2.new(0, rootButton.Position.X.Scale + 105, 0, rootButton.Position.Y.Scale - ((count - 3) * 100)); }; count = count + 1 if type(list[msg]) == 'table' then text_List[rootButton][chatText] = Chat:CreateSafeChatOptions(list[msg], chatText) else --table.insert(text_List[chatText], true) end chatText.MouseEnter:connect(function() Chat:ToggleSafeChatMenu(chatText) end) chatText.MouseLeave:connect(function() Chat:ToggleSafeChatMenu(chatText) end) chatText.MouseButton1Click:connect(function() local lList = Chat:FindButtonTree(chatText) if lList then for i, v in pairs(lList) do end else end pcall(function() PlayersService:Chat(lList[1]) end) end) end end return text_List end function Chat:CreateSafeChatGui() self.SafeChatFrame = Gui.Create'Frame' { Name = 'SafeChatFrame'; Size = UDim2.new(1, 0, 1, 0); Parent = self.Gui; BackgroundTransparency = 1.0; Gui.Create'ImageButton' { Name = 'SafeChatButton'; Size = UDim2.new(0, 44, 0, 31); Position = UDim2.new(0, 1, 0.35, 0); BackgroundTransparency = 1.0; Image = 'http://www.roblox.com/asset/?id=97080365'; }; } self.SafeChatButton = self.SafeChatFrame.SafeChatButton -- safe chat button is the root of this tree self.SafeChatTree[self.SafeChatButton] = Chat:CreateSafeChatOptions(self.SafeChat_List, self.SafeChatButton) self.SafeChatButton.MouseButton1Click:connect(function() Chat:ToggleSafeChatMenu(self.SafeChatButton) end) end function Chat:FocusOnChatBar() if self.ClickToChatButton then self.ClickToChatButton.Visible = false end self.GotFocus = true if self.Frame['Background'] then self.Frame.Background.Visible = false end self.ChatBar:CaptureFocus() end -- For touch devices we create a button instead function Chat:CreateTouchButton() self.ChatTouchFrame = Gui.Create'Frame' { Name = 'ChatTouchFrame'; Size = UDim2.new(0, 128, 0, 32); Position = UDim2.new(0, 88, 0, 0); BackgroundTransparency = 1.0; Parent = self.Gui; Gui.Create'ImageButton' { Name = 'ChatLabel'; Size = UDim2.new(0, 74, 0, 28); Position = UDim2.new(0, 0, 0, 0); BackgroundTransparency = 1.0; ZIndex = 2.0; }; Gui.Create'ImageLabel' { Name = 'Background'; Size = UDim2.new(1, 0, 1, 0); Position = UDim2.new(0, 0, 0, 0); BackgroundTransparency = 1.0; Image = 'http://www.roblox.com/asset/?id=97078724' }; } self.TapToChatLabel = self.ChatTouchFrame.ChatLabel self.TouchLabelBackground = self.ChatTouchFrame.Background self.ChatBar = Gui.Create'TextBox' { Name = 'ChatBar'; Size = UDim2.new(1, 0, 0.2, 0); Position = UDim2.new(0, 0, 0.8, 800); Text = ""; ZIndex = 1.0; BackgroundTransparency = 1.0; Parent = self.Frame; TextXAlignment = Enum.TextXAlignment.Left; TextColor3 = Color3.new(1, 1, 1); ClearTextOnFocus = false; }; self.TapToChatLabel.MouseButton1Click:connect(function() self.TapToChatLabel.Visible = false --self.ChatBar.Visible = true --self.Frame.Background.Visible = true self.ChatBar:CaptureFocus() self.GotFocus = true if self.TouchLabelBackground then self.TouchLabelBackground.Visible = false end end) end -- Non touch devices, create the bottom chat bar function Chat:CreateChatBar() -- okay now we do -- local status, result = pcall(function() return GuiService.UseLuaChat end) -- if forceChatGUI or (status and result) then -- self.ClickToChatButton = Gui.Create'TextButton' -- { -- Name = 'ClickToChat'; -- Size = UDim2.new(1, 0, 0, 20); -- BackgroundTransparency = 1.0; -- ZIndex = 2.0; -- Parent = self.Gui; -- Text = "To chat click here or press \"/\" key"; -- TextColor3 = Color3.new(1, 1, 0.9); -- Position = UDim2.new(0, 0, 1, 0); -- TextXAlignment = Enum.TextXAlignment.Left; -- FontSize = Enum.FontSize.Size12; -- } -- -- self.ChatBar = Gui.Create'TextBox' -- { -- Name = 'ChatBar'; -- Size = UDim2.new(1, 0, 0, 20); -- Position = UDim2.new(0, 0, 1, 0); -- Text = ""; -- ZIndex = 1.0; -- BackgroundColor3 = Color3.new(0, 0, 0); -- BackgroundTransparency = 0.25; -- Parent = self.Gui; -- TextXAlignment = Enum.TextXAlignment.Left; -- TextColor3 = Color3.new(1, 1, 1); -- FontSize = Enum.FontSize.Size12; -- ClearTextOnFocus = false; -- Text = ''; -- }; -- -- -- Engine has code to offset the entire world, so if we do it by -20 pixels nothing gets in our chat's way -- --GuiService:SetGlobalSizeOffsetPixel(0, -20) -- local success, error = pcall(function() GuiService:SetGlobalGuiInset(0, 0, 0, 20) end) -- if not success then -- GuiService:SetGlobalSizeOffsetPixel(0, -20) -- end -- -- CHatHotKey is '/' -- GuiService:AddSpecialKey(Enum.SpecialKey.ChatHotkey) -- GuiService.SpecialKeyPressed:connect(function(key) -- if key == Enum.SpecialKey.ChatHotkey then -- Chat:FocusOnChatBar() -- end -- end) -- -- self.ClickToChatButton.MouseButton1Click:connect(function() -- Chat:FocusOnChatBar() -- end) -- end end -- Create the initial Chat stuff -- Done only once function Chat:CreateGui() self.Gui = script.Parent self.Frame = Gui.Create'Frame' { Name = 'ChatFrame'; --Size = self.Configuration.Size; Size = UDim2.new(0, 500, 0, 120); Position = UDim2.new(0, 0, 0, 5); BackgroundTransparency = 1.0; --ClipsDescendants = true; ZIndex = 0.0; Parent = self.Gui; Active = false; Gui.Create'ImageLabel' { Name = 'Background'; Image = 'http://www.roblox.com/asset/?id=97120937'; --96551212'; Size = UDim2.new(1.3, 0, 1.64, 0); Position = UDim2.new(0, 0, 0, 0); BackgroundTransparency = 1.0; ZIndex = 0.0; Visible = false }; Gui.Create'Frame' { Name = 'Border'; Size = UDim2.new(1, 0, 0, 1); Position = UDim2.new(0, 0, 0.8, 0); BackgroundTransparency = 0.0; BackgroundColor3 = Color3.new(236/255, 236/255, 236/255); BorderSizePixel = 0.0; Visible = false; }; Gui.Create'Frame' { Name = 'ChatRenderFrame'; Size = UDim2.new(1.02, 0, 1.01, 0); Position = UDim2.new(0, 0, 0, 0); BackgroundTransparency = 1.0; --ClipsDescendants = true; ZIndex = 0.0; Active = false; }; }; Spawn(function() wait(0.5) if IsPhone() then self.Frame.Size = UDim2.new(0, 280, 0, 120) end end) self.RenderFrame = self.Frame.ChatRenderFrame if Chat:IsTouchDevice() then self.Frame.Position = self.Configuration.Position; self.RenderFrame.Size = UDim2.new(1, 0, 1, 0) elseif self.Frame.AbsoluteSize.Y > 120 then Chat:ScreenSizeChanged() self.Gui.Changed:connect(function(property) if property == 'AbsoluteSize' then Chat:ScreenSizeChanged() end end) end if forceChatGUI or true then if Chat:IsTouchDevice() then Chat:CreateTouchButton() else Chat:CreateChatBar() --Chat:CreateSafeChatGui() end if self.ChatBar then self.ChatBar.FocusLost:connect(function(enterPressed) Chat.GotFocus = false if Chat:IsTouchDevice() then self.ChatBar.Visible = false self.TapToChatLabel.Visible = true if self.TouchLabelBackground then self.TouchLabelBackground.Visible = true end end if enterPressed and self.ChatBar.Text ~= "" then local cText = self.ChatBar.Text if string.sub(self.ChatBar.Text, 1, 1) == '%' then cText = '(TEAM) ' .. string.sub(cText, 2, #cText) pcall(function() PlayersService:TeamChat(cText) end) else pcall(function() PlayersService:Chat(cText) end) end if self.ClickToChatButton then self.ClickToChatButton.Visible = true end self.ChatBar.Text = "" end Spawn(function() wait(5.0) if not Chat.GotFocus then Chat.Frame.Background.Visible = false end end) end) end end end -- Scrolling function -- Applies a speed(velocity) to have nice scrolling effect function Input:OnMouseScroll() Spawn(function() -- How long should the speed last? while Input.Speed ~=0 do if Input.Speed > 1 then while Input.Speed > 0 do Input.Speed = Input.Speed - 1 wait(0.25) end elseif Input.Speed < 0 then while Input.Speed < 0 do Input.Speed = Input.Speed + 1 wait(0.25) end end wait(0.03) end end) if Chat:CheckIfInBounds(Input.Speed) then return end Chat:ScrollQueue() end function Input:ApplySpeed(value) Input.Speed = Input.Speed + value if not self.Simulating then Input:OnMouseScroll() end end function Input:Initialize() self.Mouse.WheelBackward:connect(function() Input:ApplySpeed(self.Configuration.DefaultSpeed) end) self.Mouse.WheelForward:connect(function() Input:ApplySpeed(self.Configuration.DefaultSpeed) end) end function Chat:FindMessageInSafeChat(message, list) local foundMessage = false for msg, _ in pairs(list) do if msg == message then return true end if type(list[msg]) == 'table' then foundMessage = Chat:FindMessageInSafeChat(message, list[msg]) if foundMessage then return true end end end return foundMessage end -- Just a wrapper around our PlayerChatted event function Chat:PlayerChatted(...) local args = {...} local argCount = select('#', ...) local player local message -- This doesn't look very good, but what else to do? if args[2] then player = args[2] end if args[3] then message = args[3] if string.sub(message, 1, 1) == '%' then message = '(TEAM) ' .. string.sub(message, 2, #message) end end -- if PlayersService.ClassicChat then if string.sub(message, 1, 3) == '/e ' or string.sub(message, 1, 7) == '/emote ' then -- don't do anything right now elseif forceChatGUI or true then Chat:UpdateChat(player, message) elseif true and string.sub(message, 1, 3) == '/sc' then Chat:UpdateChat(player, message) else if Chat:FindMessageInSafeChat(message, self.SafeChat_List) then Chat:UpdateChat(player, message) end end -- end end -- After Chat.Configuration.Lifetime seconds of existence, the labels become invisible -- Runs only every 5 seconds and has to loop through 50 values -- Shouldn't be too expensive function Chat:CullThread() while true do if #self.MessageQueue > 0 then for _, field in pairs(self.MessageQueue) do if field['SpawnTime'] and field['Player'] and field['Message'] and tick() - field['SpawnTime'] > self.Configuration.LifeTime then field['Player'].Visible = false field['Message'].Visible = false end end end wait(5.0) end end -- RobloxLock everything so users can't delete them(?) function Chat:LockAllFields(gui) local children = gui:GetChildren() for i = 1, #children do children[i].RobloxLocked = true if #children[i]:GetChildren() > 0 then Chat:LockAllFields(children[i]) end end end function Chat:CoreGuiChanged(coreGuiType,enabled) if coreGuiType == Enum.CoreGuiType.Chat or coreGuiType == Enum.CoreGuiType.All then if self.Frame then self.Frame.Visible = enabled end if not Chat:IsTouchDevice() and self.ChatBar then self.ChatBar.Visible = enabled if enabled then GuiService:SetGlobalGuiInset(0, 0, 0, 20) else GuiService:SetGlobalGuiInset(0, 0, 0, 0) end end end end _G.ChatHistory = _G.ChatHistory or {} function RunChatHistory(max) for i=#_G.ChatHistory-max, #_G.ChatHistory do local player, message = unpack(_G.ChatHistory[i] or {}) if player ~= nil and message ~= nil then export:SendChat(player.Name, message, nil, true) end end end function AddToChatHistory(player, message) table.insert(_G.ChatHistory, {player, message}) end -- Constructor -- This function initializes everything function Chat:Initialize() Chat:CreateGui() function PlayerChatted(...) Chat:PlayerChatted(...) end function OnPlayer(p) if p:IsA 'Player' then p.Chatted:connect(function(chat) PlayerChatted( Enum.PlayerChatType.All, p, chat, nil ) end) end end for _,p in pairs(game.Players:GetPlayers()) do OnPlayer(p) end game.Players.ChildAdded:connect(OnPlayer) self.EventListener = script.ChildAdded:connect(function(...) -- This event has 4 callback arguments -- Enum.PlayerChatType.All, chatPlayer, message, targetPlayer -- Chat:PlayerChatted(...) end) -- Initialize input for us -- Input:Initialize() -- Eww, everytime a player is added, you have to redo the connection -- Seems this is not automatic -- -- NOTE: PlayerAdded only fires on the server, hence ChildAdded is used here -- PlayersService.ChildAdded:connect(function() -- Chat.EventListener:disconnect() -- self.EventListener = PlayersService.PlayerChatted:connect(function(...) -- -- This event has 4 callback arguments -- -- Enum.PlayerChatType.All, chatPlayer, message, targetPlayer -- Chat:PlayerChatted(...) -- end) -- end) Spawn(function() Chat:CullThread() end) -- Chat:LockAllFields(self.Frame) -- self.Frame.DescendantAdded:connect(function(descendant) ---- Chat:LockAllFields(descendant) -- end) end Chat:Initialize() RunChatHistory(10) _G.ChatAPI = export