mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,981 @@
|
||||
--[[
|
||||
//FileName: ChatScript.LUA
|
||||
//Written by: Sorcus
|
||||
//Description: Code for lua side chat on ROBLOX. Supports Scrolling.
|
||||
//NOTE: If you find any bugs or inaccuracies PM Sorcus on ROBLOX or @Canavus on Twitter
|
||||
]]
|
||||
|
||||
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 cGui = Game:GetService('CoreGui')
|
||||
local rGui = WaitForChild(cGui, 'RobloxGui')
|
||||
if rGui.AbsoluteSize.Y < 600 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
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,nstr)
|
||||
-- %s+ stands whitespaces
|
||||
-- We yank out any whitespaces at the begin and end of the string
|
||||
-- After that, we put a tab behind newlines
|
||||
-- That way people can't fake messages on a new line
|
||||
return str:match("^%s*(.-)%s*$"):gsub("\n","\n"..nstr)
|
||||
end
|
||||
|
||||
while Game:GetService("Players").LocalPlayer == nil do wait(0.03) end
|
||||
|
||||
local Player = Game:GetService("Players").LocalPlayer
|
||||
while Player.Character == nil do wait(0.03) end
|
||||
local RbxUtility = LoadLibrary('RbxUtility')
|
||||
local Gui = typedef(RbxUtility)
|
||||
local Camera = Game:GetService("Workspace").CurrentCamera
|
||||
|
||||
-- Services
|
||||
local CoreGuiService = Game:GetService('CoreGui')
|
||||
local PlayersService = Game:GetService('Players')
|
||||
local DebrisService= Game:GetService('Debris')
|
||||
local GuiService = Game:GetService('GuiService')
|
||||
local inputService = game:GetService("UserInputService")
|
||||
|
||||
-- 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.Size18, -- 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,
|
||||
HaltTime = 1/15, -- Why would people need to be chatting faster than every 1/15th of a second?
|
||||
},
|
||||
|
||||
PreviousMessage = tick(), -- Timestamp of previous message
|
||||
|
||||
-- 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 = {
|
||||
'watrbx', 'watrabi', 'VMware', 'Frantic', 'SolidSoirb', 'MugMan', 'Sword',
|
||||
},
|
||||
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)
|
||||
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 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 = inputService.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') or label:IsA('ImageLabel') then
|
||||
if diff then
|
||||
label.Position = label.Position - UDim2.new(0, 0, diff, 0)
|
||||
else
|
||||
local yOffset = 0
|
||||
local xOffset = 20
|
||||
if label:IsA('ImageLabel') then
|
||||
yOffset = 4
|
||||
xOffset = 0
|
||||
end
|
||||
if field == self.MessageQueue[i] then
|
||||
label.Position = UDim2.new(self.Configuration.XScale, xOffset, label.Position.Y.Scale - field['Message'].Size.Y.Scale , yOffset)
|
||||
-- Just to show up popping effect for the latest message in chat
|
||||
if label:IsA('TextLabel') or label:IsA('TextButton') then
|
||||
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.6
|
||||
else
|
||||
label.TextStrokeTransparency = 1.0
|
||||
end
|
||||
end)
|
||||
else
|
||||
Spawn(function()
|
||||
wait(0.05)
|
||||
while label.ImageTransparency > 0 do
|
||||
label.ImageTransparency = label.ImageTransparency - 0.2
|
||||
wait(0.03)
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
else
|
||||
label.Position = UDim2.new(self.Configuration.XScale, xOffset, label.Position.Y.Scale - field['Message'].Size.Y.Scale, yOffset)
|
||||
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 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
|
||||
|
||||
-- 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
|
||||
local pLabel,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
|
||||
|
||||
local nString = ""
|
||||
|
||||
|
||||
pLabel = Gui.Create'ImageLabel'
|
||||
{
|
||||
Name = pName;
|
||||
Parent = self.RenderFrame;
|
||||
Size = UDim2.new(0, 14, 0, 14);
|
||||
BackgroundTransparency = 1.0;
|
||||
Position = UDim2.new(0, 0, 1, -10);
|
||||
BorderSizePixel = 0.0;
|
||||
Image = "rbxasset://textures/ui/chat_teamButton.png";
|
||||
ImageTransparency = 1.0;
|
||||
};
|
||||
|
||||
local pColor
|
||||
if cPlayer.Neutral then
|
||||
pLabel.ImageColor3 = Chat:ComputeChatColor(pName)
|
||||
else
|
||||
pLabel.ImageColor3 = cPlayer.TeamColor.Color
|
||||
end
|
||||
|
||||
mLabel = Gui.Create'TextLabel'
|
||||
{
|
||||
Name = pName .. ' - message';
|
||||
-- Max is 3 lines
|
||||
Size = UDim2.new(1, 0, 0.5, 0);
|
||||
TextColor3 = Chat.Configuration.MessageColor;
|
||||
Font = Enum.Font.SourceSans;
|
||||
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, 40, 1, 0);
|
||||
BorderSizePixel = 0.0;
|
||||
TextStrokeColor3 = Color3.new(0, 0, 0);
|
||||
TextStrokeTransparency = 0.6;
|
||||
--Active = false;
|
||||
};
|
||||
mLabel.Text = nString .. pName .. ": " .. message;
|
||||
|
||||
if not pName then
|
||||
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)
|
||||
|
||||
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: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.watrbx.wtf/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
|
||||
pcall(function() GuiService:SetGlobalSizeOffsetPixel(0, -20) end) -- Doesn't hurt to throw a non-existent function into a pcall
|
||||
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(CoreGuiService, 'RobloxGui')
|
||||
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.watrbx.wtf/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
|
||||
-- leave space for the settings button on touch devices
|
||||
-- better use the exact same test it uses for its position
|
||||
if game:GetService("UserInputService").TouchEnabled then
|
||||
self.Frame.Position = UDim2.new(0, 0, 0, 55)
|
||||
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 Player.ChatMode == Enum.ChatMode.TextAndMenu then
|
||||
if Chat:IsTouchDevice() then
|
||||
Chat:CreateTouchButton()
|
||||
else
|
||||
Chat:CreateChatBar()
|
||||
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
|
||||
|
||||
if tick() - Chat.PreviousMessage > Chat.Configuration.HaltTime then -- Make sure that the user isn't deliberately spamming the chat
|
||||
Chat.PreviousMessage = tick()
|
||||
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
|
||||
end
|
||||
Spawn(function()
|
||||
wait(5.0)
|
||||
if not Chat.GotFocus then
|
||||
Chat.Frame.Background.Visible = false
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
-- Make the escape key clear the chat box (like it used to)
|
||||
inputService.InputBegan:connect(function(input)
|
||||
if (input.KeyCode == Enum.KeyCode.Escape) then
|
||||
if self.ClickToChatButton then
|
||||
self.ClickToChatButton.Visible = true
|
||||
end
|
||||
|
||||
self.ChatBar.Text = ""
|
||||
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
|
||||
|
||||
-- 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 Player.ChatMode == Enum.ChatMode.TextAndMenu then
|
||||
Chat:UpdateChat(player, message)
|
||||
elseif Player.ChatMode == Enum.ChatMode.Menu and string.sub(message, 1, 3) == '/sc' then
|
||||
Chat:UpdateChat(player, message)
|
||||
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 self.TapToChatLabel then self.TapToChatLabel.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
|
||||
|
||||
-- Constructor
|
||||
-- This function initializes everything
|
||||
function Chat:Initialize()
|
||||
|
||||
Chat:CreateGui()
|
||||
|
||||
pcall(function()
|
||||
Chat:CoreGuiChanged(Enum.CoreGuiType.Chat, Game:GetService("StarterGui"):GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
|
||||
Game:GetService("StarterGui").CoreGuiChangedSignal:connect(function(coreGuiType,enabled) Chat:CoreGuiChanged(coreGuiType,enabled) end)
|
||||
end)
|
||||
|
||||
self.EventListener = PlayersService.PlayerChatted:connect(function(...)
|
||||
-- This event has 4 callback arguments
|
||||
-- Enum.PlayerChatType.All, chatPlayer, message, targetPlayer
|
||||
Chat:PlayerChatted(...)
|
||||
|
||||
end)
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
self.Frame.RobloxLocked = true
|
||||
Chat:LockAllFields(self.Frame)
|
||||
self.Frame.DescendantAdded:connect(function(descendant)
|
||||
Chat:LockAllFields(descendant)
|
||||
end)
|
||||
end
|
||||
|
||||
Chat:Initialize()
|
||||
Reference in New Issue
Block a user