931 lines
24 KiB
Plaintext
931 lines
24 KiB
Plaintext
--!strict
|
|
-- CoreGui.RobloxGui.CoreScripts/ChatScript
|
|
print "[Mercury]: Loaded corescript 97188756"
|
|
|
|
local CoreGui = game:GetService "CoreGui"
|
|
local GuiService = game:GetService "GuiService"
|
|
local Players = game:GetService "Players"
|
|
local RunService = game:GetService "RunService"
|
|
|
|
|
|
local SafeChat = require "../Modules/Safechat.yml" -- THANK YOU DARKLUA
|
|
local New = require("../Modules/New").New
|
|
local BaseUrl = require "../Modules/BaseUrl"
|
|
local path = BaseUrl.path
|
|
|
|
local forceChatGUI = false
|
|
|
|
-- Utility functions + Globals
|
|
local function WaitForChild(parent: Instance, childName)
|
|
while parent:FindFirstChild(childName) == nil do
|
|
parent.ChildAdded:wait(0.03)
|
|
end
|
|
return parent[childName]
|
|
end
|
|
|
|
while not Players.LocalPlayer do
|
|
RunService.Heartbeat:wait()
|
|
end
|
|
|
|
local Player = Players.LocalPlayer
|
|
while not Player.Character do
|
|
RunService.Heartbeat:wait()
|
|
end
|
|
local Camera = workspace.CurrentCamera
|
|
|
|
-- Lua Enums
|
|
local 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: { any } = {}
|
|
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,
|
|
}
|
|
local function CreateEnum(enumName, 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
|
|
---------------------------------------------------
|
|
------------------ Input class --------------------
|
|
local Input = {
|
|
Mouse = Player:GetMouse(),
|
|
Speed = 0,
|
|
|
|
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.35, 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 = { "taskmanager", "Heliodex", "tako" },
|
|
|
|
SafeChat_List = SafeChat,
|
|
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 -= 1
|
|
end
|
|
if reverseIndex % 4 >= 2 then
|
|
cValue = -cValue
|
|
end
|
|
value += cValue
|
|
end
|
|
return value % 8
|
|
end
|
|
|
|
function Chat:ComputeChatColor(pName)
|
|
return self.ChatColors[GetNameValue(pName) + 1].Color
|
|
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 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
|
|
|
|
-- 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 + 1]
|
|
local Previous = self.MessageQueue[i - 1]
|
|
if
|
|
label
|
|
and type(label) == "userdata" -- until i figure out what's going on
|
|
and (label:IsA "TextLabel" or label:IsA "TextButton")
|
|
then
|
|
print(
|
|
"scrolling",
|
|
value,
|
|
"|",
|
|
Previous,
|
|
"|",
|
|
Previous and Previous.message or "none",
|
|
"|",
|
|
Next,
|
|
"|",
|
|
Next and Next.message or "none"
|
|
)
|
|
-- if value > 0 and Previous and Previous.Message then
|
|
-- -- label.Position = previous.Message.Position
|
|
-- label.Position += UDim2.new(0, 0, 1, 0)
|
|
-- elseif value < 1 and Next.Message then
|
|
-- -- label.Position = previous.Message.Position
|
|
-- label.Position -= UDim2.new(0, 0, 1, 0)
|
|
-- end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Handles the rendering of the text objects in their appropriate places
|
|
function Chat:UpdateQueue(field, diff)
|
|
print "Updating queue"
|
|
-- 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
|
|
Spawn(function()
|
|
wait(0.05)
|
|
while label.TextTransparency >= 0 do
|
|
label.TextTransparency = label.TextTransparency
|
|
- 0.2
|
|
wait(0.03)
|
|
end
|
|
if label == field.Message then
|
|
label.TextStrokeTransparency = 0.8
|
|
else
|
|
label.TextStrokeTransparency = 1
|
|
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
|
|
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 = New "TextButton" {
|
|
Size = UDim2.new(
|
|
0,
|
|
pLabel.AbsoluteSize.X,
|
|
0,
|
|
pLabel.AbsoluteSize.Y
|
|
),
|
|
FontSize = self.Configuration.FontSize,
|
|
Parent = self.RenderFrame,
|
|
BackgroundTransparency = 1,
|
|
Text = nString,
|
|
Name = "SpaceButton",
|
|
}
|
|
else
|
|
self.TempSpaceLabel.Text = nString
|
|
end
|
|
|
|
while self.TempSpaceLabel.TextBounds.X < pLabel.TextBounds.X do
|
|
nString ..= " "
|
|
self.TempSpaceLabel.Text = nString
|
|
end
|
|
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 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
|
|
|
|
-- NOTE: Temporarily disabled ring buffer to allow for chat to always wrap around
|
|
function Chat:CreateMessage(cPlayer, message)
|
|
local pName
|
|
if not cPlayer then
|
|
pName = ""
|
|
else
|
|
pName = cPlayer.Name
|
|
end
|
|
-- Users can use enough white spaces to spoof chatting as other players
|
|
-- This removes trailing and leading white spaces
|
|
-- AFAIK, there is no reason for spam white spaces
|
|
-- %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
|
|
message = string.gsub(message, "^%s*(.-)%s*$", "%1")
|
|
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
|
|
|
|
-- 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 = New "TextLabel" {
|
|
Name = pName,
|
|
Text = `{pName}:`,
|
|
-- TextColor3 = 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,
|
|
TextTransparency = 1,
|
|
Position = UDim2.new(0, 0, 1, 0),
|
|
BorderSizePixel = 0,
|
|
TextStrokeColor3 = Color3.new(0.5, 0.5, 0.5),
|
|
TextStrokeTransparency = 0.75,
|
|
--Active = false;
|
|
}
|
|
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 = New "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,
|
|
TextTransparency = 1,
|
|
Position = UDim2.new(0, 0, 1, 0),
|
|
BorderSizePixel = 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
|
|
|
|
for _, adminName in pairs(self.Admins_List) do
|
|
if string.lower(adminName) == string.lower(pName) then
|
|
mLabel.TextColor3 = self.Configuration.AdminMessageColor
|
|
end
|
|
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 queueField = {
|
|
Player = pLabel,
|
|
Message = mLabel,
|
|
SpawnTime = tick(), -- Used for identifying when to make the message invisible
|
|
}
|
|
|
|
table.insert(self.MessageQueue, 1, queueField)
|
|
Chat:UpdateQueue(queueField)
|
|
end
|
|
|
|
function Chat:ScreenSizeChanged()
|
|
RunService.Heartbeat:wait()
|
|
while self.Frame.AbsoluteSize.Y > 120 do
|
|
(self.Frame :: 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: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
|
|
|
|
-- 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 = New "TextButton" {
|
|
Name = "ClickToChat",
|
|
Size = UDim2.new(1, 0, 0, 20),
|
|
BackgroundTransparency = 1,
|
|
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 = New "TextBox" {
|
|
Name = "ChatBar",
|
|
Size = UDim2.new(1, 0, 0, 20),
|
|
Position = UDim2.new(0, 0, 1, 0),
|
|
Text = "",
|
|
ZIndex = 1,
|
|
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,
|
|
}
|
|
|
|
-- 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 ok = pcall(function()
|
|
GuiService:SetGlobalGuiInset(0, 0, 0, 20)
|
|
end)
|
|
if not ok 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 = WaitForChild(CoreGui, "RobloxGui")
|
|
self.Frame = New "Frame" {
|
|
Name = "ChatFrame",
|
|
--Size = self.Configuration.Size;
|
|
Size = UDim2.new(0, 500, 0, 120),
|
|
Position = UDim2.new(0, 0, 0, 5),
|
|
BackgroundTransparency = 1,
|
|
--ClipsDescendants = true;
|
|
ZIndex = 0,
|
|
Parent = self.Gui,
|
|
Active = false,
|
|
|
|
New "ImageLabel" {
|
|
Name = "Background",
|
|
Image = path "asset?id=97120937", -- 96551212
|
|
Size = UDim2.new(1.3, 0, 1.64, 0),
|
|
Position = UDim2.new(0, 0, 0, 0),
|
|
BackgroundTransparency = 1,
|
|
ZIndex = 0,
|
|
Visible = false,
|
|
},
|
|
|
|
New "Frame" {
|
|
Name = "Border",
|
|
Size = UDim2.new(1, 0, 0, 1),
|
|
Position = UDim2.new(0, 0, 0.8, 0),
|
|
BackgroundTransparency = 0,
|
|
BackgroundColor3 = Color3.new(236 / 255, 236 / 255, 236 / 255),
|
|
BorderSizePixel = 0,
|
|
Visible = false,
|
|
},
|
|
|
|
New "Frame" {
|
|
Name = "ChatRenderFrame",
|
|
Size = UDim2.new(1.02, 0, 1.01, 0),
|
|
Position = UDim2.new(0, 0, 0, 0),
|
|
BackgroundTransparency = 1,
|
|
--ClipsDescendants = true;
|
|
ZIndex = 0,
|
|
Active = false,
|
|
},
|
|
}
|
|
|
|
self.RenderFrame = self.Frame.ChatRenderFrame
|
|
if self.Frame.AbsoluteSize.Y > 120 then
|
|
Chat:ScreenSizeChanged()
|
|
self.Gui.Changed:connect(function(property)
|
|
if property == "AbsoluteSize" then
|
|
Chat:ScreenSizeChanged()
|
|
end
|
|
end)
|
|
end
|
|
|
|
if not (forceChatGUI or Player.ChatMode == Enum.ChatMode.TextAndMenu) then
|
|
return
|
|
end
|
|
Chat:CreateChatBar()
|
|
if not self.ChatBar then
|
|
return
|
|
end
|
|
|
|
self.ChatBar.FocusLost:connect(function(enterPressed)
|
|
Chat.GotFocus = false
|
|
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(Players.TeamChat, Players, cText)
|
|
else
|
|
pcall(Players.Chat, Players, cText)
|
|
end
|
|
|
|
if self.ClickToChatButton then
|
|
self.ClickToChatButton.Visible = true
|
|
end
|
|
self.ChatBar.Text = ""
|
|
end
|
|
Spawn(function()
|
|
wait(5)
|
|
if not Chat.GotFocus then
|
|
Chat.Frame.Background.Visible = false
|
|
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 -= 1
|
|
wait(0.25)
|
|
end
|
|
elseif Input.Speed < 0 then
|
|
while Input.Speed < 0 do
|
|
Input.Speed += 1
|
|
wait(0.25)
|
|
end
|
|
end
|
|
wait(0.03)
|
|
end
|
|
end)
|
|
if Chat:CheckIfInBounds(Input.Speed) then
|
|
return
|
|
end
|
|
Chat:ScrollQueue(Input.Speed)
|
|
end
|
|
|
|
function Input:ApplySpeed(value)
|
|
Input.Speed += value
|
|
Input:OnMouseScroll()
|
|
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)
|
|
for msg, _ in pairs(list) do
|
|
if
|
|
msg == message
|
|
or type(list[msg]) == "table"
|
|
and Chat:FindMessageInSafeChat(message, list[msg])
|
|
then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
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
|
|
Players.ClassicChat
|
|
and (not (string.sub(message, 1, 3) == "/e " or string.sub(
|
|
message,
|
|
1,
|
|
7
|
|
) == "/emote ") and (forceChatGUI or Player.ChatMode == Enum.ChatMode.TextAndMenu) or (Player.ChatMode == Enum.ChatMode.Menu and string.sub(
|
|
message,
|
|
1,
|
|
3
|
|
) == "/sc"))
|
|
or (Chat:FindMessageInSafeChat(message, self.SafeChat_List))
|
|
then
|
|
Chat:UpdateChat(player, message)
|
|
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)
|
|
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 self.ChatBar then
|
|
self.ChatBar.Visible = enabled
|
|
GuiService:SetGlobalGuiInset(0, 0, 0, enabled and 20 or 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Constructor
|
|
-- This function initializes everything
|
|
function Chat:Initialize()
|
|
Chat:CreateGui()
|
|
|
|
pcall(function()
|
|
Chat:CoreGuiChanged(
|
|
Enum.CoreGuiType.Chat,
|
|
game.StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Chat)
|
|
)
|
|
game.StarterGui.CoreGuiChangedSignal:connect(
|
|
function(coreGuiType, enabled)
|
|
Chat:CoreGuiChanged(coreGuiType, enabled)
|
|
end
|
|
)
|
|
end)
|
|
|
|
local function chatted(...)
|
|
-- This event has 4 callback arguments
|
|
-- Enum.PlayerChatType.All, chatPlayer, message, targetPlayer
|
|
Chat:PlayerChatted(...)
|
|
end
|
|
|
|
self.EventListener = Players.PlayerChatted:connect(chatted)
|
|
|
|
self.MessageThread = coroutine.create(function() end)
|
|
coroutine.resume(self.MessageThread)
|
|
|
|
-- 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
|
|
Players.ChildAdded:connect(function()
|
|
Chat.EventListener:disconnect()
|
|
self.EventListener = Players.PlayerChatted:connect(chatted)
|
|
end)
|
|
|
|
Spawn(function()
|
|
Chat:CullThread()
|
|
end)
|
|
|
|
self.Frame.RobloxLocked = true
|
|
Chat:LockAllFields(self.Frame)
|
|
self.Frame.DescendantAdded:connect(function(descendant)
|
|
Chat:LockAllFields(descendant)
|
|
end)
|
|
end
|
|
|
|
Chat:Initialize()
|