add gs
This commit is contained in:
+711
@@ -0,0 +1,711 @@
|
||||
--[[
|
||||
// FileName: BubbleChat.lua
|
||||
// Written by: jeditkacheff, TheGamer101
|
||||
// Description: Code for rendering bubble chat
|
||||
]]
|
||||
|
||||
--[[ SERVICES ]]
|
||||
local PlayersService = game:GetService('Players')
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local ChatService = game:GetService("Chat")
|
||||
local TextService = game:GetService("TextService")
|
||||
--[[ END OF SERVICES ]]
|
||||
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
while LocalPlayer == nil do
|
||||
PlayersService.ChildAdded:wait()
|
||||
LocalPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
|
||||
|
||||
local okShouldClipInGameChat, valueShouldClipInGameChat = pcall(function() return UserSettings():IsUserFeatureEnabled("UserShouldClipInGameChat") end)
|
||||
local shouldClipInGameChat = okShouldClipInGameChat and valueShouldClipInGameChat
|
||||
|
||||
--[[ SCRIPT VARIABLES ]]
|
||||
local CHAT_BUBBLE_FONT = Enum.Font.SourceSans
|
||||
local CHAT_BUBBLE_FONT_SIZE = Enum.FontSize.Size24 -- if you change CHAT_BUBBLE_FONT_SIZE_INT please change this to match
|
||||
local CHAT_BUBBLE_FONT_SIZE_INT = 24 -- if you change CHAT_BUBBLE_FONT_SIZE please change this to match
|
||||
local CHAT_BUBBLE_LINE_HEIGHT = CHAT_BUBBLE_FONT_SIZE_INT + 10
|
||||
local CHAT_BUBBLE_TAIL_HEIGHT = 14
|
||||
local CHAT_BUBBLE_WIDTH_PADDING = 30
|
||||
local CHAT_BUBBLE_FADE_SPEED = 1.5
|
||||
|
||||
local BILLBOARD_MAX_WIDTH = 400
|
||||
local BILLBOARD_MAX_HEIGHT = 250 --This limits the number of bubble chats that you see above characters
|
||||
|
||||
local ELIPSES = "..."
|
||||
local MaxChatMessageLength = 128 -- max chat message length, including null terminator and elipses.
|
||||
local MaxChatMessageLengthExclusive = MaxChatMessageLength - string.len(ELIPSES) - 1
|
||||
|
||||
local NEAR_BUBBLE_DISTANCE = 65 --previously 45
|
||||
local MAX_BUBBLE_DISTANCE = 100 --previously 80
|
||||
|
||||
--[[ END OF SCRIPT VARIABLES ]]
|
||||
|
||||
|
||||
-- [[ SCRIPT ENUMS ]]
|
||||
local BubbleColor = { WHITE = "dub",
|
||||
BLUE = "blu",
|
||||
GREEN = "gre",
|
||||
RED = "red" }
|
||||
|
||||
--[[ END OF SCRIPT ENUMS ]]
|
||||
|
||||
-- This screenGui exists so that the billboardGui is not deleted when the PlayerGui is reset.
|
||||
local BubbleChatScreenGui = Instance.new("ScreenGui")
|
||||
BubbleChatScreenGui.Name = "BubbleChat"
|
||||
BubbleChatScreenGui.ResetOnSpawn = false
|
||||
BubbleChatScreenGui.Parent = PlayerGui
|
||||
|
||||
--[[ FUNCTIONS ]]
|
||||
|
||||
local function lerpLength(msg, min, max)
|
||||
return min + (max-min) * math.min(string.len(msg)/75.0, 1.0)
|
||||
end
|
||||
|
||||
local function createFifo()
|
||||
local this = {}
|
||||
this.data = {}
|
||||
|
||||
local emptyEvent = Instance.new("BindableEvent")
|
||||
this.Emptied = emptyEvent.Event
|
||||
|
||||
function this:Size()
|
||||
return #this.data
|
||||
end
|
||||
|
||||
function this:Empty()
|
||||
return this:Size() <= 0
|
||||
end
|
||||
|
||||
function this:PopFront()
|
||||
table.remove(this.data, 1)
|
||||
if this:Empty() then emptyEvent:Fire() end
|
||||
end
|
||||
|
||||
function this:Front()
|
||||
return this.data[1]
|
||||
end
|
||||
|
||||
function this:Get(index)
|
||||
return this.data[index]
|
||||
end
|
||||
|
||||
function this:PushBack(value)
|
||||
table.insert(this.data, value)
|
||||
end
|
||||
|
||||
function this:GetData()
|
||||
return this.data
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
local function createCharacterChats()
|
||||
local this = {}
|
||||
|
||||
this.Fifo = createFifo()
|
||||
this.BillboardGui = nil
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
local function createMap()
|
||||
local this = {}
|
||||
this.data = {}
|
||||
local count = 0
|
||||
|
||||
function this:Size()
|
||||
return count
|
||||
end
|
||||
|
||||
function this:Erase(key)
|
||||
if this.data[key] then count = count - 1 end
|
||||
this.data[key] = nil
|
||||
end
|
||||
|
||||
function this:Set(key, value)
|
||||
this.data[key] = value
|
||||
if value then count = count + 1 end
|
||||
end
|
||||
|
||||
function this:Get(key)
|
||||
if not key then return end
|
||||
if not this.data[key] then
|
||||
this.data[key] = createCharacterChats()
|
||||
local emptiedCon = nil
|
||||
emptiedCon = this.data[key].Fifo.Emptied:connect(function()
|
||||
emptiedCon:disconnect()
|
||||
this:Erase(key)
|
||||
end)
|
||||
end
|
||||
return this.data[key]
|
||||
end
|
||||
|
||||
function this:GetData()
|
||||
return this.data
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
local function createChatLine(message, bubbleColor, isLocalPlayer)
|
||||
local this = {}
|
||||
|
||||
function this:ComputeBubbleLifetime(msg, isSelf)
|
||||
if isSelf then
|
||||
return lerpLength(msg,8,15)
|
||||
else
|
||||
return lerpLength(msg,12,20)
|
||||
end
|
||||
end
|
||||
|
||||
this.Origin = nil
|
||||
this.RenderBubble = nil
|
||||
this.Message = message
|
||||
this.BubbleDieDelay = this:ComputeBubbleLifetime(message, isLocalPlayer)
|
||||
this.BubbleColor = bubbleColor
|
||||
this.IsLocalPlayer = isLocalPlayer
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
local function createPlayerChatLine(player, message, isLocalPlayer)
|
||||
local this = createChatLine(message, BubbleColor.WHITE, isLocalPlayer)
|
||||
|
||||
if player then
|
||||
this.User = player.Name
|
||||
this.Origin = player.Character
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
local function createGameChatLine(origin, message, isLocalPlayer, bubbleColor)
|
||||
local this = createChatLine(message, bubbleColor, isLocalPlayer)
|
||||
this.Origin = origin
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
function createChatBubbleMain(filePrefix, sliceRect)
|
||||
local chatBubbleMain = Instance.new("ImageLabel")
|
||||
chatBubbleMain.Name = "ChatBubble"
|
||||
chatBubbleMain.ScaleType = Enum.ScaleType.Slice
|
||||
chatBubbleMain.SliceCenter = sliceRect
|
||||
chatBubbleMain.Image = "rbxasset://textures/" .. tostring(filePrefix) .. ".png"
|
||||
chatBubbleMain.BackgroundTransparency = 1
|
||||
chatBubbleMain.BorderSizePixel = 0
|
||||
chatBubbleMain.Size = UDim2.new(1.0, 0, 1.0, 0)
|
||||
chatBubbleMain.Position = UDim2.new(0,0,0,0)
|
||||
|
||||
return chatBubbleMain
|
||||
end
|
||||
|
||||
function createChatBubbleTail(position, size)
|
||||
local chatBubbleTail = Instance.new("ImageLabel")
|
||||
chatBubbleTail.Name = "ChatBubbleTail"
|
||||
chatBubbleTail.Image = "rbxasset://textures/ui/dialog_tail.png"
|
||||
chatBubbleTail.BackgroundTransparency = 1
|
||||
chatBubbleTail.BorderSizePixel = 0
|
||||
chatBubbleTail.Position = position
|
||||
chatBubbleTail.Size = size
|
||||
|
||||
return chatBubbleTail
|
||||
end
|
||||
|
||||
function createChatBubbleWithTail(filePrefix, position, size, sliceRect)
|
||||
local chatBubbleMain = createChatBubbleMain(filePrefix, sliceRect)
|
||||
|
||||
local chatBubbleTail = createChatBubbleTail(position, size)
|
||||
chatBubbleTail.Parent = chatBubbleMain
|
||||
|
||||
return chatBubbleMain
|
||||
end
|
||||
|
||||
function createScaledChatBubbleWithTail(filePrefix, frameScaleSize, position, sliceRect)
|
||||
local chatBubbleMain = createChatBubbleMain(filePrefix, sliceRect)
|
||||
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Name = "ChatBubbleTailFrame"
|
||||
frame.BackgroundTransparency = 1
|
||||
frame.SizeConstraint = Enum.SizeConstraint.RelativeXX
|
||||
frame.Position = UDim2.new(0.5, 0, 1, 0)
|
||||
frame.Size = UDim2.new(frameScaleSize, 0, frameScaleSize, 0)
|
||||
frame.Parent = chatBubbleMain
|
||||
|
||||
local chatBubbleTail = createChatBubbleTail(position, UDim2.new(1,0,0.5,0))
|
||||
chatBubbleTail.Parent = frame
|
||||
|
||||
return chatBubbleMain
|
||||
end
|
||||
|
||||
function createChatImposter(filePrefix, dotDotDot, yOffset)
|
||||
local result = Instance.new("ImageLabel")
|
||||
result.Name = "DialogPlaceholder"
|
||||
result.Image = "rbxasset://textures/" .. tostring(filePrefix) .. ".png"
|
||||
result.BackgroundTransparency = 1
|
||||
result.BorderSizePixel = 0
|
||||
result.Position = UDim2.new(0, 0, -1.25, 0)
|
||||
result.Size = UDim2.new(1, 0, 1, 0)
|
||||
|
||||
local image = Instance.new("ImageLabel")
|
||||
image.Name = "DotDotDot"
|
||||
image.Image = "rbxasset://textures/" .. tostring(dotDotDot) .. ".png"
|
||||
image.BackgroundTransparency = 1
|
||||
image.BorderSizePixel = 0
|
||||
image.Position = UDim2.new(0.001, 0, yOffset, 0)
|
||||
image.Size = UDim2.new(1, 0, 0.7, 0)
|
||||
image.Parent = result
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local this = {}
|
||||
this.ChatBubble = {}
|
||||
this.ChatBubbleWithTail = {}
|
||||
this.ScalingChatBubbleWithTail = {}
|
||||
this.CharacterSortedMsg = createMap()
|
||||
|
||||
-- init chat bubble tables
|
||||
local function initChatBubbleType(chatBubbleType, fileName, imposterFileName, isInset, sliceRect)
|
||||
this.ChatBubble[chatBubbleType] = createChatBubbleMain(fileName, sliceRect)
|
||||
this.ChatBubbleWithTail[chatBubbleType] = createChatBubbleWithTail(fileName, UDim2.new(0.5, -CHAT_BUBBLE_TAIL_HEIGHT, 1, isInset and -1 or 0), UDim2.new(0, 30, 0, CHAT_BUBBLE_TAIL_HEIGHT), sliceRect)
|
||||
this.ScalingChatBubbleWithTail[chatBubbleType] = createScaledChatBubbleWithTail(fileName, 0.5, UDim2.new(-0.5, 0, 0, isInset and -1 or 0), sliceRect)
|
||||
end
|
||||
|
||||
initChatBubbleType(BubbleColor.WHITE, "ui/dialog_white", "ui/chatBubble_white_notify_bkg", false, Rect.new(5,5,15,15))
|
||||
initChatBubbleType(BubbleColor.BLUE, "ui/dialog_blue", "ui/chatBubble_blue_notify_bkg", true, Rect.new(7,7,33,33))
|
||||
initChatBubbleType(BubbleColor.RED, "ui/dialog_red", "ui/chatBubble_red_notify_bkg", true, Rect.new(7,7,33,33))
|
||||
initChatBubbleType(BubbleColor.GREEN, "ui/dialog_green", "ui/chatBubble_green_notify_bkg", true, Rect.new(7,7,33,33))
|
||||
|
||||
function this:SanitizeChatLine(msg)
|
||||
if string.len(msg) > MaxChatMessageLengthExclusive then
|
||||
return string.sub(msg, 1, MaxChatMessageLengthExclusive + string.len(ELIPSES))
|
||||
else
|
||||
return msg
|
||||
end
|
||||
end
|
||||
|
||||
local function createBillboardInstance(adornee)
|
||||
local billboardGui = Instance.new("BillboardGui")
|
||||
billboardGui.Adornee = adornee
|
||||
billboardGui.Size = UDim2.new(0,BILLBOARD_MAX_WIDTH,0,BILLBOARD_MAX_HEIGHT)
|
||||
billboardGui.StudsOffset = Vector3.new(0, 1.5, 2)
|
||||
billboardGui.Parent = BubbleChatScreenGui
|
||||
|
||||
local billboardFrame = Instance.new("Frame")
|
||||
billboardFrame.Name = "BillboardFrame"
|
||||
billboardFrame.Size = UDim2.new(1,0,1,0)
|
||||
billboardFrame.Position = UDim2.new(0,0,-0.5,0)
|
||||
billboardFrame.BackgroundTransparency = 1
|
||||
billboardFrame.Parent = billboardGui
|
||||
|
||||
local billboardChildRemovedCon = nil
|
||||
billboardChildRemovedCon = billboardFrame.ChildRemoved:connect(function()
|
||||
if #billboardFrame:GetChildren() <= 1 then
|
||||
billboardChildRemovedCon:disconnect()
|
||||
billboardGui:Destroy()
|
||||
end
|
||||
end)
|
||||
|
||||
this:CreateSmallTalkBubble(BubbleColor.WHITE).Parent = billboardFrame
|
||||
|
||||
return billboardGui
|
||||
end
|
||||
|
||||
function this:CreateBillboardGuiHelper(instance, onlyCharacter)
|
||||
if instance and not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
|
||||
if not onlyCharacter then
|
||||
if instance:IsA("BasePart") then
|
||||
-- Create a new billboardGui object attached to this player
|
||||
local billboardGui = createBillboardInstance(instance)
|
||||
this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if instance:IsA("Model") then
|
||||
local head = instance:FindFirstChild("Head")
|
||||
if head and head:IsA("BasePart") then
|
||||
-- Create a new billboardGui object attached to this player
|
||||
local billboardGui = createBillboardInstance(head)
|
||||
this.CharacterSortedMsg:Get(instance)["BillboardGui"] = billboardGui
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function distanceToBubbleOrigin(origin)
|
||||
if not origin then return 100000 end
|
||||
|
||||
return (origin.Position - game.Workspace.CurrentCamera.CoordinateFrame.p).magnitude
|
||||
end
|
||||
|
||||
local function isPartOfLocalPlayer(adornee)
|
||||
if adornee and PlayersService.LocalPlayer.Character then
|
||||
return adornee:IsDescendantOf(PlayersService.LocalPlayer.Character)
|
||||
end
|
||||
end
|
||||
|
||||
function this:SetBillboardLODNear(billboardGui)
|
||||
local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
|
||||
billboardGui.Size = UDim2.new(0, BILLBOARD_MAX_WIDTH, 0, BILLBOARD_MAX_HEIGHT)
|
||||
billboardGui.StudsOffset = Vector3.new(0, isLocalPlayer and 1.5 or 2.5, isLocalPlayer and 2 or 0.1)
|
||||
billboardGui.Enabled = true
|
||||
local billChildren = billboardGui.BillboardFrame:GetChildren()
|
||||
for i = 1, #billChildren do
|
||||
billChildren[i].Visible = true
|
||||
end
|
||||
billboardGui.BillboardFrame.SmallTalkBubble.Visible = false
|
||||
end
|
||||
|
||||
function this:SetBillboardLODDistant(billboardGui)
|
||||
local isLocalPlayer = isPartOfLocalPlayer(billboardGui.Adornee)
|
||||
billboardGui.Size = UDim2.new(4,0,3,0)
|
||||
billboardGui.StudsOffset = Vector3.new(0, 3, isLocalPlayer and 2 or 0.1)
|
||||
billboardGui.Enabled = true
|
||||
local billChildren = billboardGui.BillboardFrame:GetChildren()
|
||||
for i = 1, #billChildren do
|
||||
billChildren[i].Visible = false
|
||||
end
|
||||
billboardGui.BillboardFrame.SmallTalkBubble.Visible = true
|
||||
end
|
||||
|
||||
function this:SetBillboardLODVeryFar(billboardGui)
|
||||
billboardGui.Enabled = false
|
||||
end
|
||||
|
||||
function this:SetBillboardGuiLOD(billboardGui, origin)
|
||||
if not origin then return end
|
||||
|
||||
if origin:IsA("Model") then
|
||||
local head = origin:FindFirstChild("Head")
|
||||
if not head then origin = origin.PrimaryPart
|
||||
else origin = head end
|
||||
end
|
||||
|
||||
local bubbleDistance = distanceToBubbleOrigin(origin)
|
||||
|
||||
if bubbleDistance < NEAR_BUBBLE_DISTANCE then
|
||||
this:SetBillboardLODNear(billboardGui)
|
||||
elseif bubbleDistance >= NEAR_BUBBLE_DISTANCE and bubbleDistance < MAX_BUBBLE_DISTANCE then
|
||||
this:SetBillboardLODDistant(billboardGui)
|
||||
else
|
||||
this:SetBillboardLODVeryFar(billboardGui)
|
||||
end
|
||||
end
|
||||
|
||||
function this:CameraCFrameChanged()
|
||||
for index, value in pairs(this.CharacterSortedMsg:GetData()) do
|
||||
local playerBillboardGui = value["BillboardGui"]
|
||||
if playerBillboardGui then this:SetBillboardGuiLOD(playerBillboardGui, index) end
|
||||
end
|
||||
end
|
||||
|
||||
function this:CreateBubbleText(message)
|
||||
local bubbleText = Instance.new("TextLabel")
|
||||
bubbleText.Name = "BubbleText"
|
||||
bubbleText.BackgroundTransparency = 1
|
||||
bubbleText.Position = UDim2.new(0,CHAT_BUBBLE_WIDTH_PADDING/2,0,0)
|
||||
bubbleText.Size = UDim2.new(1,-CHAT_BUBBLE_WIDTH_PADDING,1,0)
|
||||
bubbleText.Font = CHAT_BUBBLE_FONT
|
||||
if shouldClipInGameChat then
|
||||
bubbleText.ClipsDescendants = true
|
||||
end
|
||||
bubbleText.TextWrapped = true
|
||||
bubbleText.FontSize = CHAT_BUBBLE_FONT_SIZE
|
||||
bubbleText.Text = message
|
||||
bubbleText.Visible = false
|
||||
|
||||
return bubbleText
|
||||
end
|
||||
|
||||
function this:CreateSmallTalkBubble(chatBubbleType)
|
||||
local smallTalkBubble = this.ScalingChatBubbleWithTail[chatBubbleType]:Clone()
|
||||
smallTalkBubble.Name = "SmallTalkBubble"
|
||||
smallTalkBubble.AnchorPoint = Vector2.new(0, 0.5)
|
||||
smallTalkBubble.Position = UDim2.new(0,0,0.5,0)
|
||||
smallTalkBubble.Visible = false
|
||||
local text = this:CreateBubbleText("...")
|
||||
text.TextScaled = true
|
||||
text.TextWrapped = false
|
||||
text.Visible = true
|
||||
text.Parent = smallTalkBubble
|
||||
|
||||
return smallTalkBubble
|
||||
end
|
||||
|
||||
function this:UpdateChatLinesForOrigin(origin, currentBubbleYPos)
|
||||
local bubbleQueue = this.CharacterSortedMsg:Get(origin).Fifo
|
||||
local bubbleQueueSize = bubbleQueue:Size()
|
||||
local bubbleQueueData = bubbleQueue:GetData()
|
||||
if #bubbleQueueData <= 1 then return end
|
||||
|
||||
for index = (#bubbleQueueData - 1), 1, -1 do
|
||||
local value = bubbleQueueData[index]
|
||||
local bubble = value.RenderBubble
|
||||
if not bubble then return end
|
||||
local bubblePos = bubbleQueueSize - index + 1
|
||||
|
||||
if bubblePos > 1 then
|
||||
local tail = bubble:FindFirstChild("ChatBubbleTail")
|
||||
if tail then tail:Destroy() end
|
||||
local bubbleText = bubble:FindFirstChild("BubbleText")
|
||||
if bubbleText then bubbleText.TextTransparency = 0.5 end
|
||||
end
|
||||
|
||||
local udimValue = UDim2.new( bubble.Position.X.Scale, bubble.Position.X.Offset,
|
||||
1, currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT )
|
||||
bubble:TweenPosition(udimValue, Enum.EasingDirection.Out, Enum.EasingStyle.Bounce, 0.1, true)
|
||||
currentBubbleYPos = currentBubbleYPos - bubble.Size.Y.Offset - CHAT_BUBBLE_TAIL_HEIGHT
|
||||
end
|
||||
end
|
||||
|
||||
function this:DestroyBubble(bubbleQueue, bubbleToDestroy)
|
||||
if not bubbleQueue then return end
|
||||
if bubbleQueue:Empty() then return end
|
||||
|
||||
local bubble = bubbleQueue:Front().RenderBubble
|
||||
if not bubble then
|
||||
bubbleQueue:PopFront()
|
||||
return
|
||||
end
|
||||
|
||||
spawn(function()
|
||||
while bubbleQueue:Front().RenderBubble ~= bubbleToDestroy do
|
||||
wait()
|
||||
end
|
||||
|
||||
bubble = bubbleQueue:Front().RenderBubble
|
||||
|
||||
local timeBetween = 0
|
||||
local bubbleText = bubble:FindFirstChild("BubbleText")
|
||||
local bubbleTail = bubble:FindFirstChild("ChatBubbleTail")
|
||||
|
||||
while bubble and bubble.ImageTransparency < 1 do
|
||||
timeBetween = wait()
|
||||
if bubble then
|
||||
local fadeAmount = timeBetween * CHAT_BUBBLE_FADE_SPEED
|
||||
bubble.ImageTransparency = bubble.ImageTransparency + fadeAmount
|
||||
if bubbleText then bubbleText.TextTransparency = bubbleText.TextTransparency + fadeAmount end
|
||||
if bubbleTail then bubbleTail.ImageTransparency = bubbleTail.ImageTransparency + fadeAmount end
|
||||
end
|
||||
end
|
||||
|
||||
if bubble then
|
||||
bubble:Destroy()
|
||||
bubbleQueue:PopFront()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function this:CreateChatLineRender(instance, line, onlyCharacter, fifo)
|
||||
if not instance then return end
|
||||
|
||||
if not this.CharacterSortedMsg:Get(instance)["BillboardGui"] then
|
||||
this:CreateBillboardGuiHelper(instance, onlyCharacter)
|
||||
end
|
||||
|
||||
local billboardGui = this.CharacterSortedMsg:Get(instance)["BillboardGui"]
|
||||
if billboardGui then
|
||||
local chatBubbleRender = this.ChatBubbleWithTail[line.BubbleColor]:Clone()
|
||||
chatBubbleRender.Visible = false
|
||||
local bubbleText = this:CreateBubbleText(line.Message)
|
||||
|
||||
bubbleText.Parent = chatBubbleRender
|
||||
chatBubbleRender.Parent = billboardGui.BillboardFrame
|
||||
|
||||
line.RenderBubble = chatBubbleRender
|
||||
|
||||
local currentTextBounds = TextService:GetTextSize(
|
||||
bubbleText.Text, CHAT_BUBBLE_FONT_SIZE_INT, CHAT_BUBBLE_FONT,
|
||||
Vector2.new(BILLBOARD_MAX_WIDTH, BILLBOARD_MAX_HEIGHT))
|
||||
local bubbleWidthScale = math.max((currentTextBounds.X + CHAT_BUBBLE_WIDTH_PADDING)/BILLBOARD_MAX_WIDTH, 0.1)
|
||||
local numOflines = (currentTextBounds.Y/CHAT_BUBBLE_FONT_SIZE_INT)
|
||||
|
||||
-- prep chat bubble for tween
|
||||
chatBubbleRender.Size = UDim2.new(0,0,0,0)
|
||||
chatBubbleRender.Position = UDim2.new(0.5,0,1,0)
|
||||
|
||||
local newChatBubbleOffsetSizeY = numOflines * CHAT_BUBBLE_LINE_HEIGHT
|
||||
|
||||
chatBubbleRender:TweenSizeAndPosition(UDim2.new(bubbleWidthScale, 0, 0, newChatBubbleOffsetSizeY),
|
||||
UDim2.new( (1-bubbleWidthScale)/2, 0, 1, -newChatBubbleOffsetSizeY),
|
||||
Enum.EasingDirection.Out, Enum.EasingStyle.Elastic, 0.1, true,
|
||||
function() bubbleText.Visible = true end)
|
||||
|
||||
-- todo: remove when over max bubbles
|
||||
this:SetBillboardGuiLOD(billboardGui, line.Origin)
|
||||
this:UpdateChatLinesForOrigin(line.Origin, -newChatBubbleOffsetSizeY)
|
||||
|
||||
delay(line.BubbleDieDelay, function()
|
||||
this:DestroyBubble(fifo, chatBubbleRender)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function this:OnPlayerChatMessage(sourcePlayer, message, targetPlayer)
|
||||
if not this:BubbleChatEnabled() then return end
|
||||
|
||||
local localPlayer = PlayersService.LocalPlayer
|
||||
local fromOthers = localPlayer ~= nil and sourcePlayer ~= localPlayer
|
||||
|
||||
local safeMessage = this:SanitizeChatLine(message)
|
||||
|
||||
local line = createPlayerChatLine(sourcePlayer, safeMessage, not fromOthers)
|
||||
|
||||
if sourcePlayer and line.Origin then
|
||||
local fifo = this.CharacterSortedMsg:Get(line.Origin).Fifo
|
||||
fifo:PushBack(line)
|
||||
--Game chat (badges) won't show up here
|
||||
this:CreateChatLineRender(sourcePlayer.Character, line, true, fifo)
|
||||
end
|
||||
end
|
||||
|
||||
function this:OnGameChatMessage(origin, message, color)
|
||||
local localPlayer = PlayersService.LocalPlayer
|
||||
local fromOthers = localPlayer ~= nil and (localPlayer.Character ~= origin)
|
||||
|
||||
local bubbleColor = BubbleColor.WHITE
|
||||
|
||||
if color == Enum.ChatColor.Blue then bubbleColor = BubbleColor.BLUE
|
||||
elseif color == Enum.ChatColor.Green then bubbleColor = BubbleColor.GREEN
|
||||
elseif color == Enum.ChatColor.Red then bubbleColor = BubbleColor.RED end
|
||||
|
||||
local safeMessage = this:SanitizeChatLine(message)
|
||||
local line = createGameChatLine(origin, safeMessage, not fromOthers, bubbleColor)
|
||||
|
||||
this.CharacterSortedMsg:Get(line.Origin).Fifo:PushBack(line)
|
||||
this:CreateChatLineRender(origin, line, false, this.CharacterSortedMsg:Get(line.Origin).Fifo)
|
||||
end
|
||||
|
||||
function this:BubbleChatEnabled()
|
||||
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
|
||||
if clientChatModules then
|
||||
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
|
||||
if chatSettings then
|
||||
local chatSettings = require(chatSettings)
|
||||
if chatSettings.BubbleChatEnabled ~= nil then
|
||||
return chatSettings.BubbleChatEnabled
|
||||
end
|
||||
end
|
||||
end
|
||||
return PlayersService.BubbleChat
|
||||
end
|
||||
|
||||
function this:ShowOwnFilteredMessage()
|
||||
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
|
||||
if clientChatModules then
|
||||
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
|
||||
if chatSettings then
|
||||
chatSettings = require(chatSettings)
|
||||
return chatSettings.ShowUserOwnFilteredMessage
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function findPlayer(playerName)
|
||||
for i,v in pairs(PlayersService:GetPlayers()) do
|
||||
if v.Name == playerName then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatService.Chatted:connect(function(origin, message, color) this:OnGameChatMessage(origin, message, color) end)
|
||||
|
||||
local cameraChangedCon = nil
|
||||
if game.Workspace.CurrentCamera then
|
||||
cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
|
||||
end
|
||||
|
||||
game.Workspace.Changed:connect(function(prop)
|
||||
if prop == "CurrentCamera" then
|
||||
if cameraChangedCon then cameraChangedCon:disconnect() end
|
||||
if game.Workspace.CurrentCamera then
|
||||
cameraChangedCon = game.Workspace.CurrentCamera:GetPropertyChangedSignal("CFrame"):connect(function(prop) this:CameraCFrameChanged() end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
local AllowedMessageTypes = nil
|
||||
|
||||
function getAllowedMessageTypes()
|
||||
if AllowedMessageTypes then
|
||||
return AllowedMessageTypes
|
||||
end
|
||||
local clientChatModules = ChatService:FindFirstChild("ClientChatModules")
|
||||
if clientChatModules then
|
||||
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
|
||||
if chatSettings then
|
||||
chatSettings = require(chatSettings)
|
||||
if chatSettings.BubbleChatMessageTypes then
|
||||
AllowedMessageTypes = chatSettings.BubbleChatMessageTypes
|
||||
return AllowedMessageTypes
|
||||
end
|
||||
end
|
||||
local chatConstants = clientChatModules:FindFirstChild("ChatConstants")
|
||||
if chatConstants then
|
||||
chatConstants = require(chatConstants)
|
||||
AllowedMessageTypes = {chatConstants.MessageTypeDefault, chatConstants.MessageTypeWhisper}
|
||||
end
|
||||
return AllowedMessageTypes
|
||||
end
|
||||
return {"Message", "Whisper"}
|
||||
end
|
||||
|
||||
function checkAllowedMessageType(messageData)
|
||||
local allowedMessageTypes = getAllowedMessageTypes()
|
||||
for i = 1, #allowedMessageTypes do
|
||||
if allowedMessageTypes[i] == messageData.MessageType then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local ChatEvents = ReplicatedStorage:WaitForChild("DefaultChatSystemChatEvents")
|
||||
local OnMessageDoneFiltering = ChatEvents:WaitForChild("OnMessageDoneFiltering")
|
||||
local OnNewMessage = ChatEvents:WaitForChild("OnNewMessage")
|
||||
|
||||
OnNewMessage.OnClientEvent:connect(function(messageData, channelName)
|
||||
if not checkAllowedMessageType(messageData) then
|
||||
return
|
||||
end
|
||||
|
||||
local sender = findPlayer(messageData.FromSpeaker)
|
||||
if not sender then
|
||||
return
|
||||
end
|
||||
|
||||
if not messageData.IsFiltered or messageData.FromSpeaker == LocalPlayer.Name then
|
||||
if messageData.FromSpeaker ~= LocalPlayer.Name or this:ShowOwnFilteredMessage() then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
this:OnPlayerChatMessage(sender, messageData.Message, nil)
|
||||
end)
|
||||
|
||||
OnMessageDoneFiltering.OnClientEvent:connect(function(messageData, channelName)
|
||||
if not checkAllowedMessageType(messageData) then
|
||||
return
|
||||
end
|
||||
|
||||
local sender = findPlayer(messageData.FromSpeaker)
|
||||
if not sender then
|
||||
return
|
||||
end
|
||||
|
||||
if messageData.FromSpeaker == LocalPlayer.Name and not this:ShowOwnFilteredMessage() then
|
||||
return
|
||||
end
|
||||
|
||||
this:OnPlayerChatMessage(sender, messageData.Message, nil)
|
||||
end)
|
||||
@@ -0,0 +1,400 @@
|
||||
-- // FileName: ChannelsBar.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Manages creating, destroying, and displaying ChannelTabs.
|
||||
|
||||
local module = {}
|
||||
|
||||
local PlayerGui = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui")
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
local moduleChannelsTab = require(modulesFolder:WaitForChild("ChannelsTab"))
|
||||
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:CreateGuiObjects(targetParent)
|
||||
local BaseFrame = Instance.new("Frame")
|
||||
BaseFrame.Selectable = false
|
||||
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
BaseFrame.BackgroundTransparency = 1
|
||||
BaseFrame.Parent = targetParent
|
||||
|
||||
local ScrollingBase = Instance.new("Frame")
|
||||
ScrollingBase.Selectable = false
|
||||
ScrollingBase.Name = "ScrollingBase"
|
||||
ScrollingBase.BackgroundTransparency = 1
|
||||
ScrollingBase.ClipsDescendants = true
|
||||
ScrollingBase.Size = UDim2.new(1, 0, 1, 0)
|
||||
ScrollingBase.Position = UDim2.new(0, 0, 0, 0)
|
||||
ScrollingBase.Parent = BaseFrame
|
||||
|
||||
local ScrollerSizer = Instance.new("Frame")
|
||||
ScrollerSizer.Selectable = false
|
||||
ScrollerSizer.Name = "ScrollerSizer"
|
||||
ScrollerSizer.BackgroundTransparency = 1
|
||||
ScrollerSizer.Size = UDim2.new(1, 0, 1, 0)
|
||||
ScrollerSizer.Position = UDim2.new(0, 0, 0, 0)
|
||||
ScrollerSizer.Parent = ScrollingBase
|
||||
|
||||
local ScrollerFrame = Instance.new("Frame")
|
||||
ScrollerFrame.Selectable = false
|
||||
ScrollerFrame.Name = "ScrollerFrame"
|
||||
ScrollerFrame.BackgroundTransparency = 1
|
||||
ScrollerFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
ScrollerFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
ScrollerFrame.Parent = ScrollerSizer
|
||||
|
||||
local LeaveConfirmationFrameBase = Instance.new("Frame")
|
||||
LeaveConfirmationFrameBase.Selectable = false
|
||||
LeaveConfirmationFrameBase.Size = UDim2.new(1, 0, 1, 0)
|
||||
LeaveConfirmationFrameBase.Position = UDim2.new(0, 0, 0, 0)
|
||||
LeaveConfirmationFrameBase.ClipsDescendants = true
|
||||
LeaveConfirmationFrameBase.BackgroundTransparency = 1
|
||||
LeaveConfirmationFrameBase.Parent = BaseFrame
|
||||
|
||||
local LeaveConfirmationFrame = Instance.new("Frame")
|
||||
LeaveConfirmationFrame.Selectable = false
|
||||
LeaveConfirmationFrame.Name = "LeaveConfirmationFrame"
|
||||
LeaveConfirmationFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
LeaveConfirmationFrame.Position = UDim2.new(0, 0, 1, 0)
|
||||
LeaveConfirmationFrame.BackgroundTransparency = 0.6
|
||||
LeaveConfirmationFrame.BorderSizePixel = 0
|
||||
LeaveConfirmationFrame.BackgroundColor3 = Color3.new(0, 0, 0)
|
||||
LeaveConfirmationFrame.Parent = LeaveConfirmationFrameBase
|
||||
|
||||
local InputBlocker = Instance.new("TextButton")
|
||||
InputBlocker.Selectable = false
|
||||
InputBlocker.Size = UDim2.new(1, 0, 1, 0)
|
||||
InputBlocker.BackgroundTransparency = 1
|
||||
InputBlocker.Text = ""
|
||||
InputBlocker.Parent = LeaveConfirmationFrame
|
||||
|
||||
local LeaveConfirmationButtonYes = Instance.new("TextButton")
|
||||
LeaveConfirmationButtonYes.Selectable = false
|
||||
LeaveConfirmationButtonYes.Size = UDim2.new(0.25, 0, 1, 0)
|
||||
LeaveConfirmationButtonYes.BackgroundTransparency = 1
|
||||
LeaveConfirmationButtonYes.Font = ChatSettings.DefaultFont
|
||||
LeaveConfirmationButtonYes.TextSize = 18
|
||||
LeaveConfirmationButtonYes.TextStrokeTransparency = 0.75
|
||||
LeaveConfirmationButtonYes.Position = UDim2.new(0, 0, 0, 0)
|
||||
LeaveConfirmationButtonYes.TextColor3 = Color3.new(0, 1, 0)
|
||||
LeaveConfirmationButtonYes.Text = "Confirm"
|
||||
LeaveConfirmationButtonYes.Parent = LeaveConfirmationFrame
|
||||
|
||||
local LeaveConfirmationButtonNo = LeaveConfirmationButtonYes:Clone()
|
||||
LeaveConfirmationButtonNo.Parent = LeaveConfirmationFrame
|
||||
LeaveConfirmationButtonNo.Position = UDim2.new(0.75, 0, 0, 0)
|
||||
LeaveConfirmationButtonNo.TextColor3 = Color3.new(1, 0, 0)
|
||||
LeaveConfirmationButtonNo.Text = "Cancel"
|
||||
|
||||
local LeaveConfirmationNotice = Instance.new("TextLabel")
|
||||
LeaveConfirmationNotice.Selectable = false
|
||||
LeaveConfirmationNotice.Size = UDim2.new(0.5, 0, 1, 0)
|
||||
LeaveConfirmationNotice.Position = UDim2.new(0.25, 0, 0, 0)
|
||||
LeaveConfirmationNotice.BackgroundTransparency = 1
|
||||
LeaveConfirmationNotice.TextColor3 = Color3.new(1, 1, 1)
|
||||
LeaveConfirmationNotice.TextStrokeTransparency = 0.75
|
||||
LeaveConfirmationNotice.Text = "Leave channel <XX>?"
|
||||
LeaveConfirmationNotice.Font = ChatSettings.DefaultFont
|
||||
LeaveConfirmationNotice.TextSize = 18
|
||||
LeaveConfirmationNotice.Parent = LeaveConfirmationFrame
|
||||
|
||||
local LeaveTarget = Instance.new("StringValue")
|
||||
LeaveTarget.Name = "LeaveTarget"
|
||||
LeaveTarget.Parent = LeaveConfirmationFrame
|
||||
|
||||
local outPos = LeaveConfirmationFrame.Position
|
||||
LeaveConfirmationButtonYes.MouseButton1Click:connect(function()
|
||||
MessageSender:SendMessage(string.format("/leave %s", LeaveTarget.Value), nil)
|
||||
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
|
||||
end)
|
||||
LeaveConfirmationButtonNo.MouseButton1Click:connect(function()
|
||||
LeaveConfirmationFrame:TweenPosition(outPos, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.2, true)
|
||||
end)
|
||||
|
||||
|
||||
|
||||
local scale = 0.7
|
||||
local scaleOther = (1 - scale) / 2
|
||||
local pageButtonImage = "rbxasset://textures/ui/Chat/TabArrowBackground.png"
|
||||
local pageButtonArrowImage = "rbxasset://textures/ui/Chat/TabArrow.png"
|
||||
|
||||
--// ToDo: Remove these lines when the assets are put into trunk.
|
||||
--// These grab unchanging versions hosted on the site, and not from the content folder.
|
||||
pageButtonImage = "rbxassetid://471630199"
|
||||
pageButtonArrowImage = "rbxassetid://471630112"
|
||||
|
||||
|
||||
local PageLeftButton = Instance.new("ImageButton", BaseFrame)
|
||||
PageLeftButton.Selectable = ChatSettings.GamepadNavigationEnabled
|
||||
PageLeftButton.Name = "PageLeftButton"
|
||||
PageLeftButton.SizeConstraint = Enum.SizeConstraint.RelativeYY
|
||||
PageLeftButton.Size = UDim2.new(scale, 0, scale, 0)
|
||||
PageLeftButton.BackgroundTransparency = 1
|
||||
PageLeftButton.Position = UDim2.new(0, 4, scaleOther, 0)
|
||||
PageLeftButton.Visible = false
|
||||
PageLeftButton.Image = pageButtonImage
|
||||
local ArrowLabel = Instance.new("ImageLabel", PageLeftButton)
|
||||
ArrowLabel.Name = "ArrowLabel"
|
||||
ArrowLabel.BackgroundTransparency = 1
|
||||
ArrowLabel.Size = UDim2.new(0.4, 0, 0.4, 0)
|
||||
ArrowLabel.Image = pageButtonArrowImage
|
||||
|
||||
local PageRightButtonPositionalHelper = Instance.new("Frame", BaseFrame)
|
||||
PageRightButtonPositionalHelper.Selectable = false
|
||||
PageRightButtonPositionalHelper.BackgroundTransparency = 1
|
||||
PageRightButtonPositionalHelper.Name = "PositionalHelper"
|
||||
PageRightButtonPositionalHelper.Size = PageLeftButton.Size
|
||||
PageRightButtonPositionalHelper.SizeConstraint = PageLeftButton.SizeConstraint
|
||||
PageRightButtonPositionalHelper.Position = UDim2.new(1, 0, scaleOther, 0)
|
||||
|
||||
local PageRightButton = PageLeftButton:Clone()
|
||||
PageRightButton.Parent = PageRightButtonPositionalHelper
|
||||
PageRightButton.Name = "PageRightButton"
|
||||
PageRightButton.Size = UDim2.new(1, 0, 1, 0)
|
||||
PageRightButton.SizeConstraint = Enum.SizeConstraint.RelativeXY
|
||||
PageRightButton.Position = UDim2.new(-1, -4, 0, 0)
|
||||
|
||||
local positionOffset = UDim2.new(0.05, 0, 0, 0)
|
||||
|
||||
PageRightButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) + positionOffset
|
||||
PageLeftButton.ArrowLabel.Position = UDim2.new(0.3, 0, 0.3, 0) - positionOffset
|
||||
PageLeftButton.ArrowLabel.Rotation = 180
|
||||
|
||||
|
||||
self.GuiObject = BaseFrame
|
||||
|
||||
self.GuiObjects.BaseFrame = BaseFrame
|
||||
self.GuiObjects.ScrollerSizer = ScrollerSizer
|
||||
self.GuiObjects.ScrollerFrame = ScrollerFrame
|
||||
self.GuiObjects.PageLeftButton = PageLeftButton
|
||||
self.GuiObjects.PageRightButton = PageRightButton
|
||||
self.GuiObjects.LeaveConfirmationFrame = LeaveConfirmationFrame
|
||||
self.GuiObjects.LeaveConfirmationNotice = LeaveConfirmationNotice
|
||||
|
||||
self.GuiObjects.PageLeftButtonArrow = PageLeftButton.ArrowLabel
|
||||
self.GuiObjects.PageRightButtonArrow = PageRightButton.ArrowLabel
|
||||
self:AnimGuiObjects()
|
||||
|
||||
PageLeftButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(-1) end)
|
||||
PageRightButton.MouseButton1Click:connect(function() self:ScrollChannelsFrame(1) end)
|
||||
|
||||
self:ScrollChannelsFrame(0)
|
||||
end
|
||||
|
||||
|
||||
function methods:UpdateMessagePostedInChannel(channelName)
|
||||
local tab = self:GetChannelTab(channelName)
|
||||
if (tab) then
|
||||
tab:UpdateMessagePostedInChannel()
|
||||
else
|
||||
warn("ChannelsTab '" .. channelName .. "' does not exist!")
|
||||
end
|
||||
end
|
||||
|
||||
function methods:AddChannelTab(channelName)
|
||||
if (self:GetChannelTab(channelName)) then
|
||||
error("Channel tab '" .. channelName .. "'already exists!")
|
||||
end
|
||||
|
||||
local tab = moduleChannelsTab.new(channelName)
|
||||
tab.GuiObject.Parent = self.GuiObjects.ScrollerFrame
|
||||
self.ChannelTabs[channelName:lower()] = tab
|
||||
|
||||
self.NumTabs = self.NumTabs + 1
|
||||
self:OrganizeChannelTabs()
|
||||
|
||||
if (ChatSettings.RightClickToLeaveChannelEnabled) then
|
||||
tab.NameTag.MouseButton2Click:connect(function()
|
||||
self.LeaveConfirmationNotice.Text = string.format("Leave channel %s?", tab.ChannelName)
|
||||
self.LeaveConfirmationFrame.LeaveTarget.Value = tab.ChannelName
|
||||
self.LeaveConfirmationFrame:TweenPosition(UDim2.new(0, 0, 0, 0), Enum.EasingDirection.In, Enum.EasingStyle.Quad, 0.2, true)
|
||||
end)
|
||||
end
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
function methods:RemoveChannelTab(channelName)
|
||||
if (not self:GetChannelTab(channelName)) then
|
||||
error("Channel tab '" .. channelName .. "'does not exist!")
|
||||
end
|
||||
|
||||
local indexName = channelName:lower()
|
||||
self.ChannelTabs[indexName]:Destroy()
|
||||
self.ChannelTabs[indexName] = nil
|
||||
|
||||
self.NumTabs = self.NumTabs - 1
|
||||
self:OrganizeChannelTabs()
|
||||
end
|
||||
|
||||
function methods:GetChannelTab(channelName)
|
||||
return self.ChannelTabs[channelName:lower()]
|
||||
end
|
||||
|
||||
function methods:OrganizeChannelTabs()
|
||||
local order = {}
|
||||
|
||||
table.insert(order, self:GetChannelTab(ChatSettings.GeneralChannelName))
|
||||
table.insert(order, self:GetChannelTab("System"))
|
||||
|
||||
for tabIndexName, tab in pairs(self.ChannelTabs) do
|
||||
if (tab.ChannelName ~= ChatSettings.GeneralChannelName and tab.ChannelName ~= "System") then
|
||||
table.insert(order, tab)
|
||||
end
|
||||
end
|
||||
|
||||
for index, tab in pairs(order) do
|
||||
tab.GuiObject.Position = UDim2.new(index - 1, 0, 0, 0)
|
||||
end
|
||||
|
||||
--// Dynamic tab resizing
|
||||
self.GuiObjects.ScrollerSizer.Size = UDim2.new(1 / math.max(1, math.min(ChatSettings.ChannelsBarFullTabSize, self.NumTabs)), 0, 1, 0)
|
||||
|
||||
self:ScrollChannelsFrame(0)
|
||||
end
|
||||
|
||||
function methods:ResizeChannelTabText(textSize)
|
||||
for i, tab in pairs(self.ChannelTabs) do
|
||||
tab:SetTextSize(textSize)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ScrollChannelsFrame(dir)
|
||||
if (self.ScrollChannelsFrameLock) then return end
|
||||
self.ScrollChannelsFrameLock = true
|
||||
|
||||
local tabNumber = ChatSettings.ChannelsBarFullTabSize
|
||||
|
||||
local newPageNum = self.CurPageNum + dir
|
||||
if (newPageNum < 0) then
|
||||
newPageNum = 0
|
||||
elseif (newPageNum > 0 and newPageNum + tabNumber > self.NumTabs) then
|
||||
newPageNum = self.NumTabs - tabNumber
|
||||
end
|
||||
|
||||
self.CurPageNum = newPageNum
|
||||
|
||||
local tweenTime = 0.15
|
||||
local endPos = UDim2.new(-self.CurPageNum, 0, 0, 0)
|
||||
|
||||
self.GuiObjects.PageLeftButton.Visible = (self.CurPageNum > 0)
|
||||
self.GuiObjects.PageRightButton.Visible = (self.CurPageNum + tabNumber < self.NumTabs)
|
||||
|
||||
if dir == 0 then
|
||||
self.ScrollChannelsFrameLock = false
|
||||
return
|
||||
end
|
||||
|
||||
local function UnlockFunc()
|
||||
self.ScrollChannelsFrameLock = false
|
||||
end
|
||||
|
||||
self:WaitUntilParentedCorrectly()
|
||||
|
||||
self.GuiObjects.ScrollerFrame:TweenPosition(endPos, Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, tweenTime, true, UnlockFunc)
|
||||
end
|
||||
|
||||
function methods:FadeOutBackground(duration)
|
||||
for channelName, channelObj in pairs(self.ChannelTabs) do
|
||||
channelObj:FadeOutBackground(duration)
|
||||
end
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 1
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInBackground(duration)
|
||||
for channelName, channelObj in pairs(self.ChannelTabs) do
|
||||
channelObj:FadeInBackground(duration)
|
||||
end
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeOutText(duration)
|
||||
for channelName, channelObj in pairs(self.ChannelTabs) do
|
||||
channelObj:FadeOutText(duration)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:FadeInText(duration)
|
||||
for channelName, channelObj in pairs(self.ChannelTabs) do
|
||||
channelObj:FadeInText(duration)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:AnimGuiObjects()
|
||||
self.GuiObjects.PageLeftButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.PageRightButton.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.PageLeftButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.PageRightButtonArrow.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
end
|
||||
|
||||
function methods:InitializeAnimParams()
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_CurrentTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
|
||||
end
|
||||
|
||||
function methods:Update(dtScale)
|
||||
for channelName, channelObj in pairs(self.ChannelTabs) do
|
||||
channelObj:Update(dtScale)
|
||||
end
|
||||
|
||||
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Background_CurrentTransparency,
|
||||
self.AnimParams.Background_TargetTransparency,
|
||||
self.AnimParams.Background_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
|
||||
self:AnimGuiObjects()
|
||||
end
|
||||
|
||||
--// ToDo: Move to common modules
|
||||
function methods:WaitUntilParentedCorrectly()
|
||||
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
|
||||
self.GuiObject.AncestryChanged:wait()
|
||||
end
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.GuiObject = nil
|
||||
obj.GuiObjects = {}
|
||||
|
||||
obj.ChannelTabs = {}
|
||||
obj.NumTabs = 0
|
||||
obj.CurPageNum = 0
|
||||
|
||||
obj.ScrollChannelsFrameLock = false
|
||||
|
||||
obj.AnimParams = {}
|
||||
|
||||
obj:InitializeAnimParams()
|
||||
|
||||
ChatSettings.SettingsChanged:connect(function(setting, value)
|
||||
if (setting == "ChatChannelsTabTextSize") then
|
||||
obj:ResizeChannelTabText(value)
|
||||
end
|
||||
end)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,310 @@
|
||||
-- // FileName: ChannelsTab.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Channel tab button for selecting current channel and also displaying if currently selected.
|
||||
|
||||
local module = {}
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
local function CreateGuiObjects()
|
||||
local BaseFrame = Instance.new("Frame")
|
||||
BaseFrame.Selectable = false
|
||||
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
BaseFrame.BackgroundTransparency = 1
|
||||
|
||||
local gapOffsetX = 1
|
||||
local gapOffsetY = 1
|
||||
|
||||
local BackgroundFrame = Instance.new("Frame")
|
||||
BackgroundFrame.Selectable = false
|
||||
BackgroundFrame.Name = "BackgroundFrame"
|
||||
BackgroundFrame.Size = UDim2.new(1, -gapOffsetX * 2, 1, -gapOffsetY * 2)
|
||||
BackgroundFrame.Position = UDim2.new(0, gapOffsetX, 0, gapOffsetY)
|
||||
BackgroundFrame.BackgroundTransparency = 1
|
||||
BackgroundFrame.Parent = BaseFrame
|
||||
|
||||
local UnselectedFrame = Instance.new("Frame")
|
||||
UnselectedFrame.Selectable = false
|
||||
UnselectedFrame.Name = "UnselectedFrame"
|
||||
UnselectedFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
UnselectedFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
UnselectedFrame.BorderSizePixel = 0
|
||||
UnselectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabUnselectedColor
|
||||
UnselectedFrame.BackgroundTransparency = 0.6
|
||||
UnselectedFrame.Parent = BackgroundFrame
|
||||
|
||||
local SelectedFrame = Instance.new("Frame")
|
||||
SelectedFrame.Selectable = false
|
||||
SelectedFrame.Name = "SelectedFrame"
|
||||
SelectedFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
SelectedFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
SelectedFrame.BorderSizePixel = 0
|
||||
SelectedFrame.BackgroundColor3 = ChatSettings.ChannelsTabSelectedColor
|
||||
SelectedFrame.BackgroundTransparency = 1
|
||||
SelectedFrame.Parent = BackgroundFrame
|
||||
|
||||
local SelectedFrameBackgroundImage = Instance.new("ImageLabel")
|
||||
SelectedFrameBackgroundImage.Selectable = false
|
||||
SelectedFrameBackgroundImage.Name = "BackgroundImage"
|
||||
SelectedFrameBackgroundImage.BackgroundTransparency = 1
|
||||
SelectedFrameBackgroundImage.BorderSizePixel = 0
|
||||
SelectedFrameBackgroundImage.Size = UDim2.new(1, 0, 1, 0)
|
||||
SelectedFrameBackgroundImage.Position = UDim2.new(0, 0, 0, 0)
|
||||
SelectedFrameBackgroundImage.ScaleType = Enum.ScaleType.Slice
|
||||
SelectedFrameBackgroundImage.Parent = SelectedFrame
|
||||
|
||||
SelectedFrameBackgroundImage.BackgroundTransparency = 0.6 - 1
|
||||
local rate = 1.2 * 1
|
||||
SelectedFrameBackgroundImage.BackgroundColor3 = Color3.fromRGB(78 * rate, 84 * rate, 96 * rate)
|
||||
|
||||
local borderXOffset = 2
|
||||
local blueBarYSize = 4
|
||||
local BlueBarLeft = Instance.new("ImageLabel")
|
||||
BlueBarLeft.Selectable = false
|
||||
BlueBarLeft.Size = UDim2.new(0.5, -borderXOffset, 0, blueBarYSize)
|
||||
BlueBarLeft.BackgroundTransparency = 1
|
||||
BlueBarLeft.ScaleType = Enum.ScaleType.Slice
|
||||
BlueBarLeft.SliceCenter = Rect.new(3,3,32,21)
|
||||
BlueBarLeft.Parent = SelectedFrame
|
||||
|
||||
local BlueBarRight = BlueBarLeft:Clone()
|
||||
BlueBarRight.Parent = SelectedFrame
|
||||
|
||||
BlueBarLeft.Position = UDim2.new(0, borderXOffset, 1, -blueBarYSize)
|
||||
BlueBarRight.Position = UDim2.new(0.5, 0, 1, -blueBarYSize)
|
||||
BlueBarLeft.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarLeft.png"
|
||||
BlueBarRight.Image = "rbxasset://textures/ui/Settings/Slider/SelectedBarRight.png"
|
||||
|
||||
BlueBarLeft.Name = "BlueBarLeft"
|
||||
BlueBarRight.Name = "BlueBarRight"
|
||||
|
||||
local NameTag = Instance.new("TextButton")
|
||||
NameTag.Selectable = ChatSettings.GamepadNavigationEnabled
|
||||
NameTag.Size = UDim2.new(1, 0, 1, 0)
|
||||
NameTag.Position = UDim2.new(0, 0, 0, 0)
|
||||
NameTag.BackgroundTransparency = 1
|
||||
NameTag.Font = ChatSettings.DefaultFont
|
||||
NameTag.TextSize = ChatSettings.ChatChannelsTabTextSize
|
||||
NameTag.TextColor3 = Color3.new(1, 1, 1)
|
||||
NameTag.TextStrokeTransparency = 0.75
|
||||
NameTag.Parent = BackgroundFrame
|
||||
|
||||
local NameTagNonSelect = NameTag:Clone()
|
||||
local NameTagSelect = NameTag:Clone()
|
||||
NameTagNonSelect.Parent = UnselectedFrame
|
||||
NameTagSelect.Parent = SelectedFrame
|
||||
NameTagNonSelect.Font = Enum.Font.SourceSans
|
||||
NameTagNonSelect.Active = false
|
||||
NameTagSelect.Active = false
|
||||
|
||||
local NewMessageIconFrame = Instance.new("Frame")
|
||||
NewMessageIconFrame.Selectable = false
|
||||
NewMessageIconFrame.Size = UDim2.new(0, 18, 0, 18)
|
||||
NewMessageIconFrame.Position = UDim2.new(0.8, -9, 0.5, -9)
|
||||
NewMessageIconFrame.BackgroundTransparency = 1
|
||||
NewMessageIconFrame.Parent = BackgroundFrame
|
||||
|
||||
local NewMessageIcon = Instance.new("ImageLabel")
|
||||
NewMessageIcon.Selectable = false
|
||||
NewMessageIcon.Size = UDim2.new(1, 0, 1, 0)
|
||||
NewMessageIcon.BackgroundTransparency = 1
|
||||
NewMessageIcon.Image = "rbxasset://textures/ui/Chat/MessageCounter.png"
|
||||
NewMessageIcon.Visible = false
|
||||
NewMessageIcon.Parent = NewMessageIconFrame
|
||||
|
||||
local NewMessageIconText = Instance.new("TextLabel")
|
||||
NewMessageIconText.Selectable = false
|
||||
NewMessageIconText.BackgroundTransparency = 1
|
||||
NewMessageIconText.Size = UDim2.new(0, 13, 0, 9)
|
||||
NewMessageIconText.Position = UDim2.new(0.5, -7, 0.5, -7)
|
||||
NewMessageIconText.Font = ChatSettings.DefaultFont
|
||||
NewMessageIconText.TextSize = 14
|
||||
NewMessageIconText.TextColor3 = Color3.new(1, 1, 1)
|
||||
NewMessageIconText.Text = ""
|
||||
NewMessageIconText.Parent = NewMessageIcon
|
||||
|
||||
return BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame
|
||||
end
|
||||
|
||||
function methods:Destroy()
|
||||
self.GuiObject:Destroy()
|
||||
end
|
||||
|
||||
function methods:UpdateMessagePostedInChannel(ignoreActive)
|
||||
if (self.Active and (ignoreActive ~= true)) then return end
|
||||
|
||||
local count = self.UnreadMessageCount + 1
|
||||
self.UnreadMessageCount = count
|
||||
|
||||
local label = self.NewMessageIcon
|
||||
label.Visible = true
|
||||
label.TextLabel.Text = (count < 100) and tostring(count) or "!"
|
||||
|
||||
local tweenTime = 0.15
|
||||
local tweenPosOffset = UDim2.new(0, 0, -0.1, 0)
|
||||
|
||||
local curPos = label.Position
|
||||
local outPos = curPos + tweenPosOffset
|
||||
local easingDirection = Enum.EasingDirection.Out
|
||||
local easingStyle = Enum.EasingStyle.Quad
|
||||
|
||||
label.Position = UDim2.new(0, 0, -0.15, 0)
|
||||
label:TweenPosition(UDim2.new(0, 0, 0, 0), easingDirection, easingStyle, tweenTime, true)
|
||||
|
||||
end
|
||||
|
||||
function methods:SetActive(active)
|
||||
self.Active = active
|
||||
self.UnselectedFrame.Visible = not active
|
||||
self.SelectedFrame.Visible = active
|
||||
|
||||
if (active) then
|
||||
self.UnreadMessageCount = 0
|
||||
self.NewMessageIcon.Visible = false
|
||||
|
||||
self.NameTag.Font = Enum.Font.SourceSansBold
|
||||
else
|
||||
self.NameTag.Font = Enum.Font.SourceSans
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetTextSize(textSize)
|
||||
self.NameTag.TextSize = textSize
|
||||
end
|
||||
|
||||
function methods:FadeOutBackground(duration)
|
||||
self.AnimParams.Background_TargetTransparency = 1
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInBackground(duration)
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeOutText(duration)
|
||||
self.AnimParams.Text_TargetTransparency = 1
|
||||
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
self.AnimParams.TextStroke_TargetTransparency = 1
|
||||
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInText(duration)
|
||||
self.AnimParams.Text_TargetTransparency = 0
|
||||
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
self.AnimParams.TextStroke_TargetTransparency = 0.75
|
||||
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:AnimGuiObjects()
|
||||
self.UnselectedFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.SelectedFrame.BackgroundImage.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.SelectedFrame.BlueBarLeft.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.SelectedFrame.BlueBarRight.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.NameTagNonSelect.TextTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.NameTagNonSelect.TextStrokeTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
|
||||
self.NameTag.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
self.NewMessageIcon.ImageTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
self.WhiteTextNewMessageNotification.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
self.NameTagSelect.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
|
||||
self.NameTag.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
|
||||
self.WhiteTextNewMessageNotification.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
|
||||
self.NameTagSelect.TextStrokeTransparency = self.AnimParams.TextStroke_CurrentTransparency
|
||||
end
|
||||
|
||||
function methods:InitializeAnimParams()
|
||||
self.AnimParams.Text_TargetTransparency = 0
|
||||
self.AnimParams.Text_CurrentTransparency = 0
|
||||
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
|
||||
|
||||
self.AnimParams.TextStroke_TargetTransparency = 0.75
|
||||
self.AnimParams.TextStroke_CurrentTransparency = 0.75
|
||||
self.AnimParams.TextStroke_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_CurrentTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
|
||||
end
|
||||
|
||||
function methods:Update(dtScale)
|
||||
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Background_CurrentTransparency,
|
||||
self.AnimParams.Background_TargetTransparency,
|
||||
self.AnimParams.Background_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Text_CurrentTransparency,
|
||||
self.AnimParams.Text_TargetTransparency,
|
||||
self.AnimParams.Text_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
self.AnimParams.TextStroke_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.TextStroke_CurrentTransparency,
|
||||
self.AnimParams.TextStroke_TargetTransparency,
|
||||
self.AnimParams.TextStroke_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
|
||||
self:AnimGuiObjects()
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(channelName)
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
local BaseFrame, NameTag, NameTagNonSelect, NameTagSelect, NewMessageIcon, UnselectedFrame, SelectedFrame = CreateGuiObjects()
|
||||
obj.GuiObject = BaseFrame
|
||||
obj.NameTag = NameTag
|
||||
obj.NameTagNonSelect = NameTagNonSelect
|
||||
obj.NameTagSelect = NameTagSelect
|
||||
obj.NewMessageIcon = NewMessageIcon
|
||||
obj.UnselectedFrame = UnselectedFrame
|
||||
obj.SelectedFrame = SelectedFrame
|
||||
|
||||
obj.BlueBarLeft = SelectedFrame.BlueBarLeft
|
||||
obj.BlueBarRight = SelectedFrame.BlueBarRight
|
||||
obj.BackgroundImage = SelectedFrame.BackgroundImage
|
||||
obj.WhiteTextNewMessageNotification = obj.NewMessageIcon.TextLabel
|
||||
|
||||
obj.ChannelName = channelName
|
||||
obj.UnreadMessageCount = 0
|
||||
obj.Active = false
|
||||
|
||||
obj.GuiObject.Name = "Frame_" .. obj.ChannelName
|
||||
|
||||
if (string.len(channelName) > ChatSettings.MaxChannelNameLength) then
|
||||
channelName = string.sub(channelName, 1, ChatSettings.MaxChannelNameLength - 3) .. "..."
|
||||
end
|
||||
|
||||
--obj.NameTag.Text = channelName
|
||||
|
||||
obj.NameTag.Text = ""
|
||||
obj.NameTagNonSelect.Text = channelName
|
||||
obj.NameTagSelect.Text = channelName
|
||||
|
||||
obj.AnimParams = {}
|
||||
|
||||
obj:InitializeAnimParams()
|
||||
obj:AnimGuiObjects()
|
||||
obj:SetActive(false)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,576 @@
|
||||
-- // FileName: ChatBar.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Manages text typing and typing state.
|
||||
|
||||
local module = {}
|
||||
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local RunService = game:GetService("RunService")
|
||||
local Players = game:GetService("Players")
|
||||
local TextService = game:GetService("TextService")
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
|
||||
while not LocalPlayer do
|
||||
Players.PlayerAdded:wait()
|
||||
LocalPlayer = Players.LocalPlayer
|
||||
end
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
|
||||
|
||||
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:CreateGuiObjects(targetParent)
|
||||
self.ChatBarParentFrame = targetParent
|
||||
|
||||
local backgroundImagePixelOffset = 7
|
||||
local textBoxPixelOffset = 5
|
||||
|
||||
local BaseFrame = Instance.new("Frame")
|
||||
BaseFrame.Selectable = false
|
||||
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
BaseFrame.BackgroundTransparency = 0.6
|
||||
BaseFrame.BorderSizePixel = 0
|
||||
BaseFrame.BackgroundColor3 = ChatSettings.ChatBarBackGroundColor
|
||||
BaseFrame.Parent = targetParent
|
||||
|
||||
local BoxFrame = Instance.new("Frame")
|
||||
BoxFrame.Selectable = false
|
||||
BoxFrame.Name = "BoxFrame"
|
||||
BoxFrame.BackgroundTransparency = 0.6
|
||||
BoxFrame.BorderSizePixel = 0
|
||||
BoxFrame.BackgroundColor3 = ChatSettings.ChatBarBoxColor
|
||||
BoxFrame.Size = UDim2.new(1, -backgroundImagePixelOffset * 2, 1, -backgroundImagePixelOffset * 2)
|
||||
BoxFrame.Position = UDim2.new(0, backgroundImagePixelOffset, 0, backgroundImagePixelOffset)
|
||||
BoxFrame.Parent = BaseFrame
|
||||
|
||||
local TextBoxHolderFrame = Instance.new("Frame")
|
||||
TextBoxHolderFrame.BackgroundTransparency = 1
|
||||
TextBoxHolderFrame.Size = UDim2.new(1, -textBoxPixelOffset * 2, 1, -textBoxPixelOffset * 2)
|
||||
TextBoxHolderFrame.Position = UDim2.new(0, textBoxPixelOffset, 0, textBoxPixelOffset)
|
||||
TextBoxHolderFrame.Parent = BoxFrame
|
||||
|
||||
local TextBox = Instance.new("TextBox")
|
||||
TextBox.Selectable = ChatSettings.GamepadNavigationEnabled
|
||||
TextBox.Name = "ChatBar"
|
||||
TextBox.BackgroundTransparency = 1
|
||||
TextBox.Size = UDim2.new(1, 0, 1, 0)
|
||||
TextBox.Position = UDim2.new(0, 0, 0, 0)
|
||||
TextBox.TextSize = ChatSettings.ChatBarTextSize
|
||||
TextBox.Font = ChatSettings.ChatBarFont
|
||||
TextBox.TextColor3 = ChatSettings.ChatBarTextColor
|
||||
TextBox.TextTransparency = 0.4
|
||||
TextBox.TextStrokeTransparency = 1
|
||||
TextBox.ClearTextOnFocus = false
|
||||
TextBox.TextXAlignment = Enum.TextXAlignment.Left
|
||||
TextBox.TextYAlignment = Enum.TextYAlignment.Top
|
||||
TextBox.TextWrapped = true
|
||||
TextBox.Text = ""
|
||||
TextBox.Parent = TextBoxHolderFrame
|
||||
|
||||
local MessageModeTextButton = Instance.new("TextButton")
|
||||
MessageModeTextButton.Selectable = false
|
||||
MessageModeTextButton.Name = "MessageMode"
|
||||
MessageModeTextButton.BackgroundTransparency = 1
|
||||
MessageModeTextButton.Position = UDim2.new(0, 0, 0, 0)
|
||||
MessageModeTextButton.TextSize = ChatSettings.ChatBarTextSize
|
||||
MessageModeTextButton.Font = ChatSettings.ChatBarFont
|
||||
MessageModeTextButton.TextXAlignment = Enum.TextXAlignment.Left
|
||||
MessageModeTextButton.TextWrapped = true
|
||||
MessageModeTextButton.Text = ""
|
||||
MessageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
|
||||
MessageModeTextButton.TextYAlignment = Enum.TextYAlignment.Center
|
||||
MessageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
|
||||
MessageModeTextButton.Visible = true
|
||||
MessageModeTextButton.Parent = TextBoxHolderFrame
|
||||
|
||||
local TextLabel = Instance.new("TextLabel")
|
||||
TextLabel.Selectable = false
|
||||
TextLabel.TextWrapped = true
|
||||
TextLabel.BackgroundTransparency = 1
|
||||
TextLabel.Size = TextBox.Size
|
||||
TextLabel.Position = TextBox.Position
|
||||
TextLabel.TextSize = TextBox.TextSize
|
||||
TextLabel.Font = TextBox.Font
|
||||
TextLabel.TextColor3 = TextBox.TextColor3
|
||||
TextLabel.TextTransparency = TextBox.TextTransparency
|
||||
TextLabel.TextStrokeTransparency = TextBox.TextStrokeTransparency
|
||||
TextLabel.TextXAlignment = TextBox.TextXAlignment
|
||||
TextLabel.TextYAlignment = TextBox.TextYAlignment
|
||||
TextLabel.Text = "..."
|
||||
TextLabel.Parent = TextBoxHolderFrame
|
||||
|
||||
self.GuiObject = BaseFrame
|
||||
self.TextBox = TextBox
|
||||
self.TextLabel = TextLabel
|
||||
|
||||
self.GuiObjects.BaseFrame = BaseFrame
|
||||
self.GuiObjects.TextBoxFrame = BoxFrame
|
||||
self.GuiObjects.TextBox = TextBox
|
||||
self.GuiObjects.TextLabel = TextLabel
|
||||
self.GuiObjects.MessageModeTextButton = MessageModeTextButton
|
||||
|
||||
self:AnimGuiObjects()
|
||||
self:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
|
||||
if self.UserHasChatOff then
|
||||
self:DoLockChatBar()
|
||||
end
|
||||
self.eGuiObjectsChanged:Fire()
|
||||
end
|
||||
|
||||
-- Used to lock the chat bar when the user has chat turned off.
|
||||
function methods:DoLockChatBar()
|
||||
if self.TextLabel then
|
||||
if LocalPlayer.UserId > 0 then
|
||||
self.TextLabel.Text = ChatLocalization:Get(
|
||||
"GameChat_ChatMessageValidator_SettingsError",
|
||||
"To chat in game, turn on chat in your Privacy Settings."
|
||||
)
|
||||
else
|
||||
self.TextLabel.Text = ChatLocalization:Get(
|
||||
"GameChat_SwallowGuestChat_Message",
|
||||
"Sign up to chat in game."
|
||||
)
|
||||
end
|
||||
self:CalculateSize()
|
||||
end
|
||||
if self.TextBox then
|
||||
self.TextBox.Active = false
|
||||
self.TextBox.Focused:connect(function()
|
||||
self.TextBox:ReleaseFocus()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetUpTextBoxEvents(TextBox, TextLabel, MessageModeTextButton)
|
||||
-- Clean up events from a previous setup.
|
||||
for name, conn in pairs(self.TextBoxConnections) do
|
||||
conn:disconnect()
|
||||
self.TextBoxConnections[name] = nil
|
||||
end
|
||||
|
||||
--// Code for getting back into general channel from other target channel when pressing backspace.
|
||||
self.TextBoxConnections.UserInputBegan = UserInputService.InputBegan:connect(function(inputObj, gpe)
|
||||
if (inputObj.KeyCode == Enum.KeyCode.Backspace) then
|
||||
if (self:IsFocused() and TextBox.Text == "") then
|
||||
self:SetChannelTarget(ChatSettings.GeneralChannelName)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self.TextBoxConnections.TextBoxChanged = TextBox.Changed:connect(function(prop)
|
||||
if prop == "AbsoluteSize" then
|
||||
self:CalculateSize()
|
||||
return
|
||||
end
|
||||
|
||||
if prop ~= "Text" then
|
||||
return
|
||||
end
|
||||
|
||||
self:CalculateSize()
|
||||
|
||||
if (string.len(TextBox.Text) > ChatSettings.MaximumMessageLength) then
|
||||
TextBox.Text = string.sub(TextBox.Text, 1, ChatSettings.MaximumMessageLength)
|
||||
return
|
||||
end
|
||||
|
||||
if not self.InCustomState then
|
||||
local customState = self.CommandProcessor:ProcessInProgressChatMessage(TextBox.Text, self.ChatWindow, self)
|
||||
if customState then
|
||||
self.InCustomState = true
|
||||
self.CustomState = customState
|
||||
end
|
||||
else
|
||||
self.CustomState:TextUpdated()
|
||||
end
|
||||
end)
|
||||
|
||||
local function UpdateOnFocusStatusChanged(isFocused)
|
||||
if isFocused or TextBox.Text ~= "" then
|
||||
TextLabel.Visible = false
|
||||
else
|
||||
TextLabel.Visible = true
|
||||
end
|
||||
end
|
||||
|
||||
self.TextBoxConnections.MessageModeClick = MessageModeTextButton.MouseButton1Click:connect(function()
|
||||
if MessageModeTextButton.Text ~= "" then
|
||||
self:SetChannelTarget(ChatSettings.GeneralChannelName)
|
||||
end
|
||||
end)
|
||||
|
||||
self.TextBoxConnections.TextBoxFocused = TextBox.Focused:connect(function()
|
||||
if not self.UserHasChatOff then
|
||||
self:CalculateSize()
|
||||
UpdateOnFocusStatusChanged(true)
|
||||
end
|
||||
end)
|
||||
|
||||
self.TextBoxConnections.TextBoxFocusLost = TextBox.FocusLost:connect(function(enterPressed, inputObject)
|
||||
self:CalculateSize()
|
||||
if (inputObject and inputObject.KeyCode == Enum.KeyCode.Escape) then
|
||||
TextBox.Text = ""
|
||||
end
|
||||
UpdateOnFocusStatusChanged(false)
|
||||
end)
|
||||
end
|
||||
|
||||
function methods:GetTextBox()
|
||||
return self.TextBox
|
||||
end
|
||||
|
||||
function methods:GetMessageModeTextButton()
|
||||
return self.GuiObjects.MessageModeTextButton
|
||||
end
|
||||
|
||||
-- Deprecated in favour of GetMessageModeTextButton
|
||||
-- Retained for compatibility reasons.
|
||||
function methods:GetMessageModeTextLabel()
|
||||
return self:GetMessageModeTextButton()
|
||||
end
|
||||
|
||||
function methods:IsFocused()
|
||||
if self.UserHasChatOff then
|
||||
return false
|
||||
end
|
||||
|
||||
return self:GetTextBox():IsFocused()
|
||||
end
|
||||
|
||||
function methods:GetVisible()
|
||||
return self.GuiObject.Visible
|
||||
end
|
||||
|
||||
function methods:CaptureFocus()
|
||||
if not self.UserHasChatOff then
|
||||
self:GetTextBox():CaptureFocus()
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ReleaseFocus(didRelease)
|
||||
self:GetTextBox():ReleaseFocus(didRelease)
|
||||
end
|
||||
|
||||
function methods:ResetText()
|
||||
self:GetTextBox().Text = ""
|
||||
end
|
||||
|
||||
function methods:SetText(text)
|
||||
self:GetTextBox().Text = text
|
||||
end
|
||||
|
||||
function methods:GetEnabled()
|
||||
return self.GuiObject.Visible
|
||||
end
|
||||
|
||||
function methods:SetEnabled(enabled)
|
||||
if self.UserHasChatOff then
|
||||
-- The chat bar can not be removed if a user has chat turned off so that
|
||||
-- the chat bar can display a message explaining that chat is turned off.
|
||||
self.GuiObject.Visible = true
|
||||
else
|
||||
self.GuiObject.Visible = enabled
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetTextLabelText(text)
|
||||
if not self.UserHasChatOff then
|
||||
self.TextLabel.Text = text
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetTextBoxText(text)
|
||||
self.TextBox.Text = text
|
||||
end
|
||||
|
||||
function methods:GetTextBoxText()
|
||||
return self.TextBox.Text
|
||||
end
|
||||
|
||||
function methods:ResetSize()
|
||||
self.TargetYSize = 0
|
||||
self:TweenToTargetYSize()
|
||||
end
|
||||
|
||||
local function measureSize(textObj)
|
||||
return TextService:GetTextSize(
|
||||
textObj.Text,
|
||||
textObj.TextSize,
|
||||
textObj.Font,
|
||||
Vector2.new(textObj.AbsoluteSize.X, 10000)
|
||||
)
|
||||
end
|
||||
|
||||
function methods:CalculateSize()
|
||||
if self.CalculatingSizeLock then
|
||||
return
|
||||
end
|
||||
self.CalculatingSizeLock = true
|
||||
|
||||
local textSize = nil
|
||||
local bounds = nil
|
||||
|
||||
if self:IsFocused() or self.TextBox.Text ~= "" then
|
||||
textSize = self.TextBox.TextSize
|
||||
bounds = measureSize(self.TextBox).Y
|
||||
else
|
||||
textSize = self.TextLabel.TextSize
|
||||
bounds = measureSize(self.TextLabel).Y
|
||||
end
|
||||
|
||||
local newTargetYSize = bounds - textSize
|
||||
if (self.TargetYSize ~= newTargetYSize) then
|
||||
self.TargetYSize = newTargetYSize
|
||||
self:TweenToTargetYSize()
|
||||
end
|
||||
|
||||
self.CalculatingSizeLock = false
|
||||
end
|
||||
|
||||
function methods:TweenToTargetYSize()
|
||||
local endSize = UDim2.new(1, 0, 1, self.TargetYSize)
|
||||
local curSize = self.GuiObject.Size
|
||||
|
||||
local curAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
|
||||
self.GuiObject.Size = endSize
|
||||
local endAbsoluteSizeY = self.GuiObject.AbsoluteSize.Y
|
||||
self.GuiObject.Size = curSize
|
||||
|
||||
local pixelDistance = math.abs(endAbsoluteSizeY - curAbsoluteSizeY)
|
||||
local tweeningTime = math.min(1, (pixelDistance * (1 / self.TweenPixelsPerSecond))) -- pixelDistance * (seconds per pixels)
|
||||
|
||||
local success = pcall(function() self.GuiObject:TweenSize(endSize, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, tweeningTime, true) end)
|
||||
if (not success) then
|
||||
self.GuiObject.Size = endSize
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetTextSize(textSize)
|
||||
if not self:IsInCustomState() then
|
||||
if self.TextBox then
|
||||
self.TextBox.TextSize = textSize
|
||||
end
|
||||
if self.TextLabel then
|
||||
self.TextLabel.TextSize = textSize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetDefaultChannelNameColor()
|
||||
if ChatSettings.DefaultChannelNameColor then
|
||||
return ChatSettings.DefaultChannelNameColor
|
||||
end
|
||||
return Color3.fromRGB(35, 76, 142)
|
||||
end
|
||||
|
||||
function methods:SetChannelTarget(targetChannel)
|
||||
local messageModeTextButton = self.GuiObjects.MessageModeTextButton
|
||||
local textBox = self.TextBox
|
||||
local textLabel = self.TextLabel
|
||||
|
||||
self.TargetChannel = targetChannel
|
||||
|
||||
if not self:IsInCustomState() then
|
||||
if targetChannel ~= ChatSettings.GeneralChannelName then
|
||||
messageModeTextButton.Size = UDim2.new(0, 1000, 1, 0)
|
||||
messageModeTextButton.Text = string.format("[%s] ", targetChannel)
|
||||
|
||||
local channelNameColor = self:GetChannelNameColor(targetChannel)
|
||||
if channelNameColor then
|
||||
messageModeTextButton.TextColor3 = channelNameColor
|
||||
else
|
||||
messageModeTextButton.TextColor3 = self:GetDefaultChannelNameColor()
|
||||
end
|
||||
|
||||
local xSize = messageModeTextButton.TextBounds.X
|
||||
messageModeTextButton.Size = UDim2.new(0, xSize, 1, 0)
|
||||
textBox.Size = UDim2.new(1, -xSize, 1, 0)
|
||||
textBox.Position = UDim2.new(0, xSize, 0, 0)
|
||||
textLabel.Size = UDim2.new(1, -xSize, 1, 0)
|
||||
textLabel.Position = UDim2.new(0, xSize, 0, 0)
|
||||
else
|
||||
messageModeTextButton.Text = ""
|
||||
messageModeTextButton.Size = UDim2.new(0, 0, 0, 0)
|
||||
textBox.Size = UDim2.new(1, 0, 1, 0)
|
||||
textBox.Position = UDim2.new(0, 0, 0, 0)
|
||||
textLabel.Size = UDim2.new(1, 0, 1, 0)
|
||||
textLabel.Position = UDim2.new(0, 0, 0, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:IsInCustomState()
|
||||
return self.InCustomState
|
||||
end
|
||||
|
||||
function methods:ResetCustomState()
|
||||
if self.InCustomState then
|
||||
self.CustomState:Destroy()
|
||||
self.CustomState = nil
|
||||
self.InCustomState = false
|
||||
|
||||
self.ChatBarParentFrame:ClearAllChildren()
|
||||
self:CreateGuiObjects(self.ChatBarParentFrame)
|
||||
self:SetTextLabelText(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatMain_ChatBarText",
|
||||
'To chat click here or press "/" key'
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetCustomMessage()
|
||||
if self.InCustomState then
|
||||
return self.CustomState:GetMessage()
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function methods:CustomStateProcessCompletedMessage(message)
|
||||
if self.InCustomState then
|
||||
return self.CustomState:ProcessCompletedMessage()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function methods:FadeOutBackground(duration)
|
||||
self.AnimParams.Background_TargetTransparency = 1
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
self:FadeOutText(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInBackground(duration)
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
self:FadeInText(duration)
|
||||
end
|
||||
|
||||
function methods:FadeOutText(duration)
|
||||
self.AnimParams.Text_TargetTransparency = 1
|
||||
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInText(duration)
|
||||
self.AnimParams.Text_TargetTransparency = 0.4
|
||||
self.AnimParams.Text_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:AnimGuiObjects()
|
||||
self.GuiObject.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.TextBoxFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
|
||||
self.GuiObjects.TextLabel.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
self.GuiObjects.TextBox.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
self.GuiObjects.MessageModeTextButton.TextTransparency = self.AnimParams.Text_CurrentTransparency
|
||||
end
|
||||
|
||||
function methods:InitializeAnimParams()
|
||||
self.AnimParams.Text_TargetTransparency = 0.4
|
||||
self.AnimParams.Text_CurrentTransparency = 0.4
|
||||
self.AnimParams.Text_NormalizedExptValue = 1
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_CurrentTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = 1
|
||||
end
|
||||
|
||||
function methods:Update(dtScale)
|
||||
self.AnimParams.Text_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Text_CurrentTransparency,
|
||||
self.AnimParams.Text_TargetTransparency,
|
||||
self.AnimParams.Text_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Background_CurrentTransparency,
|
||||
self.AnimParams.Background_TargetTransparency,
|
||||
self.AnimParams.Background_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
|
||||
self:AnimGuiObjects()
|
||||
end
|
||||
|
||||
function methods:SetChannelNameColor(channelName, channelNameColor)
|
||||
self.ChannelNameColors[channelName] = channelNameColor
|
||||
if self.GuiObjects.MessageModeTextButton.Text == channelName then
|
||||
self.GuiObjects.MessageModeTextButton.TextColor3 = channelNameColor
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetChannelNameColor(channelName)
|
||||
return self.ChannelNameColors[channelName]
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(CommandProcessor, ChatWindow)
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.GuiObject = nil
|
||||
obj.ChatBarParentFrame = nil
|
||||
obj.TextBox = nil
|
||||
obj.TextLabel = nil
|
||||
obj.GuiObjects = {}
|
||||
obj.eGuiObjectsChanged = Instance.new("BindableEvent")
|
||||
obj.GuiObjectsChanged = obj.eGuiObjectsChanged.Event
|
||||
obj.TextBoxConnections = {}
|
||||
|
||||
obj.InCustomState = false
|
||||
obj.CustomState = nil
|
||||
|
||||
obj.TargetChannel = nil
|
||||
obj.CommandProcessor = CommandProcessor
|
||||
obj.ChatWindow = ChatWindow
|
||||
|
||||
obj.TweenPixelsPerSecond = 500
|
||||
obj.TargetYSize = 0
|
||||
|
||||
obj.AnimParams = {}
|
||||
obj.CalculatingSizeLock = false
|
||||
|
||||
obj.ChannelNameColors = {}
|
||||
|
||||
obj.UserHasChatOff = false
|
||||
|
||||
obj:InitializeAnimParams()
|
||||
|
||||
ChatSettings.SettingsChanged:connect(function(setting, value)
|
||||
if (setting == "ChatBarTextSize") then
|
||||
obj:SetTextSize(value)
|
||||
end
|
||||
end)
|
||||
|
||||
coroutine.wrap(function()
|
||||
local success, canLocalUserChat = pcall(function()
|
||||
return Chat:CanUserChatAsync(LocalPlayer.UserId)
|
||||
end)
|
||||
local canChat = success and (RunService:IsStudio() or canLocalUserChat)
|
||||
if canChat == false then
|
||||
obj.UserHasChatOff = true
|
||||
obj:DoLockChatBar()
|
||||
end
|
||||
end)()
|
||||
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,166 @@
|
||||
-- // FileName: ChatChannel.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: ChatChannel class for handling messages being added and removed from the chat channel.
|
||||
|
||||
local module = {}
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:Destroy()
|
||||
self.Destroyed = true
|
||||
end
|
||||
|
||||
function methods:SetActive(active)
|
||||
if active == self.Active then
|
||||
return
|
||||
end
|
||||
if active == false then
|
||||
self.MessageLogDisplay:Clear()
|
||||
else
|
||||
self.MessageLogDisplay:SetCurrentChannelName(self.Name)
|
||||
for i = 1, #self.MessageLog do
|
||||
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
|
||||
end
|
||||
end
|
||||
self.Active = active
|
||||
end
|
||||
|
||||
function methods:UpdateMessageFiltered(messageData)
|
||||
local searchIndex = 1
|
||||
local searchTable = self.MessageLog
|
||||
local messageObj = nil
|
||||
while (#searchTable >= searchIndex) do
|
||||
local obj = searchTable[searchIndex]
|
||||
|
||||
if (obj.ID == messageData.ID) then
|
||||
messageObj = obj
|
||||
break
|
||||
end
|
||||
|
||||
searchIndex = searchIndex + 1
|
||||
end
|
||||
|
||||
if messageObj then
|
||||
messageObj.Message = messageData.Message
|
||||
messageObj.IsFiltered = true
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:UpdateMessageFiltered(messageObj)
|
||||
end
|
||||
else
|
||||
-- We have not seen this filtered message before, but we should still add it to our log.
|
||||
self:AddMessageToChannelByTimeStamp(messageData)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:AddMessageToChannel(messageData)
|
||||
table.insert(self.MessageLog, messageData)
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:AddMessage(messageData)
|
||||
end
|
||||
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
|
||||
self:RemoveLastMessageFromChannel()
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalAddMessageAtTimeStamp(messageData)
|
||||
for i = 1, #self.MessageLog do
|
||||
if messageData.Time < self.MessageLog[i].Time then
|
||||
table.insert(self.MessageLog, i, messageData)
|
||||
return
|
||||
end
|
||||
end
|
||||
table.insert(self.MessageLog, messageData)
|
||||
end
|
||||
|
||||
function methods:AddMessagesToChannelByTimeStamp(messageLog, startIndex)
|
||||
for i = startIndex, #messageLog do
|
||||
self:InternalAddMessageAtTimeStamp(messageLog[i])
|
||||
end
|
||||
while #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel do
|
||||
table.remove(self.MessageLog, 1)
|
||||
end
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:Clear()
|
||||
for i = 1, #self.MessageLog do
|
||||
self.MessageLogDisplay:AddMessage(self.MessageLog[i])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:AddMessageToChannelByTimeStamp(messageData)
|
||||
if #self.MessageLog >= 1 then
|
||||
-- These are the fast cases to evalutate.
|
||||
if self.MessageLog[1].Time > messageData.Time then
|
||||
return
|
||||
elseif messageData.Time >= self.MessageLog[#self.MessageLog].Time then
|
||||
self:AddMessageToChannel(messageData)
|
||||
return
|
||||
end
|
||||
|
||||
for i = 1, #self.MessageLog do
|
||||
if messageData.Time < self.MessageLog[i].Time then
|
||||
table.insert(self.MessageLog, i, messageData)
|
||||
|
||||
if #self.MessageLog > ChatSettings.MessageHistoryLengthPerChannel then
|
||||
self:RemoveLastMessageFromChannel()
|
||||
end
|
||||
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:AddMessageAtIndex(messageData, i)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
else
|
||||
self:AddMessageToChannel(messageData)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RemoveLastMessageFromChannel()
|
||||
table.remove(self.MessageLog, 1)
|
||||
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:RemoveLastMessage()
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ClearMessageLog()
|
||||
self.MessageLog = {}
|
||||
|
||||
if self.Active then
|
||||
self.MessageLogDisplay:Clear()
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RegisterChannelTab(tab)
|
||||
self.ChannelTab = tab
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(channelName, messageLogDisplay)
|
||||
local obj = setmetatable({}, methods)
|
||||
obj.Destroyed = false
|
||||
obj.Active = false
|
||||
|
||||
obj.MessageLog = {}
|
||||
obj.MessageLogDisplay = messageLogDisplay
|
||||
obj.ChannelTab = nil
|
||||
obj.Name = channelName
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
||||
-- // FileName: ChatScript.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Hooks main chat module up to Topbar in corescripts.
|
||||
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local ChatService = game:GetService("Chat")
|
||||
|
||||
local MAX_COREGUI_CONNECTION_ATTEMPTS = 10
|
||||
|
||||
local ClientChatModules = ChatService:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ClientChatModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local function DoEverything()
|
||||
local Chat = require(script:WaitForChild("ChatMain"))
|
||||
|
||||
local containerTable = {}
|
||||
containerTable.ChatWindow = {}
|
||||
containerTable.SetCore = {}
|
||||
containerTable.GetCore = {}
|
||||
|
||||
containerTable.ChatWindow.ChatTypes = {}
|
||||
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = ChatSettings.BubbleChatEnabled
|
||||
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = ChatSettings.ClassicChatEnabled
|
||||
|
||||
--// Connection functions
|
||||
local function ConnectEvent(name)
|
||||
local event = Instance.new("BindableEvent")
|
||||
event.Name = name
|
||||
containerTable.ChatWindow[name] = event
|
||||
|
||||
event.Event:connect(function(...) Chat[name](Chat, ...) end)
|
||||
end
|
||||
|
||||
local function ConnectFunction(name)
|
||||
local func = Instance.new("BindableFunction")
|
||||
func.Name = name
|
||||
containerTable.ChatWindow[name] = func
|
||||
|
||||
func.OnInvoke = function(...) return Chat[name](Chat, ...) end
|
||||
end
|
||||
|
||||
local function ReverseConnectEvent(name)
|
||||
local event = Instance.new("BindableEvent")
|
||||
event.Name = name
|
||||
containerTable.ChatWindow[name] = event
|
||||
|
||||
Chat[name]:connect(function(...) event:Fire(...) end)
|
||||
end
|
||||
|
||||
local function ConnectSignal(name)
|
||||
local event = Instance.new("BindableEvent")
|
||||
event.Name = name
|
||||
containerTable.ChatWindow[name] = event
|
||||
|
||||
event.Event:connect(function(...) Chat[name]:fire(...) end)
|
||||
end
|
||||
|
||||
local function ConnectSetCore(name)
|
||||
local event = Instance.new("BindableEvent")
|
||||
event.Name = name
|
||||
containerTable.SetCore[name] = event
|
||||
|
||||
event.Event:connect(function(...) Chat[name.."Event"]:fire(...) end)
|
||||
end
|
||||
|
||||
local function ConnectGetCore(name)
|
||||
local func = Instance.new("BindableFunction")
|
||||
func.Name = name
|
||||
containerTable.GetCore[name] = func
|
||||
|
||||
func.OnInvoke = function(...) return Chat["f"..name](...) end
|
||||
end
|
||||
|
||||
--// Do connections
|
||||
ConnectEvent("ToggleVisibility")
|
||||
ConnectEvent("SetVisible")
|
||||
ConnectEvent("FocusChatBar")
|
||||
ConnectFunction("GetVisibility")
|
||||
ConnectFunction("GetMessageCount")
|
||||
ConnectEvent("TopbarEnabledChanged")
|
||||
ConnectFunction("IsFocused")
|
||||
|
||||
ReverseConnectEvent("ChatBarFocusChanged")
|
||||
ReverseConnectEvent("VisibilityStateChanged")
|
||||
ReverseConnectEvent("MessagesChanged")
|
||||
ReverseConnectEvent("MessagePosted")
|
||||
|
||||
ConnectSignal("CoreGuiEnabled")
|
||||
|
||||
ConnectSetCore("ChatMakeSystemMessage")
|
||||
ConnectSetCore("ChatWindowPosition")
|
||||
ConnectSetCore("ChatWindowSize")
|
||||
ConnectGetCore("ChatWindowPosition")
|
||||
ConnectGetCore("ChatWindowSize")
|
||||
ConnectSetCore("ChatBarDisabled")
|
||||
ConnectGetCore("ChatBarDisabled")
|
||||
|
||||
ConnectEvent("SpecialKeyPressed")
|
||||
|
||||
SetCoreGuiChatConnections(containerTable)
|
||||
end
|
||||
|
||||
function SetCoreGuiChatConnections(containerTable)
|
||||
local tries = 0
|
||||
while tries < MAX_COREGUI_CONNECTION_ATTEMPTS do
|
||||
tries = tries + 1
|
||||
local success, ret = pcall(function() StarterGui:SetCore("CoreGuiChatConnections", containerTable) end)
|
||||
if success then
|
||||
break
|
||||
end
|
||||
if not success and tries == MAX_COREGUI_CONNECTION_ATTEMPTS then
|
||||
error("Error calling SetCore CoreGuiChatConnections: " .. ret)
|
||||
end
|
||||
wait()
|
||||
end
|
||||
end
|
||||
|
||||
function checkBothChatTypesDisabled()
|
||||
if ChatSettings.BubbleChatEnabled ~= nil then
|
||||
if ChatSettings.ClassicChatEnabled ~= nil then
|
||||
return not (ChatSettings.BubbleChatEnabled or ChatSettings.ClassicChatEnabled)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
if (not GuiService:IsTenFootInterface()) and (not game:GetService('UserInputService').VREnabled) then
|
||||
if not checkBothChatTypesDisabled() then
|
||||
DoEverything()
|
||||
else
|
||||
local containerTable = {}
|
||||
containerTable.ChatWindow = {}
|
||||
|
||||
containerTable.ChatWindow.ChatTypes = {}
|
||||
containerTable.ChatWindow.ChatTypes.BubbleChatEnabled = false
|
||||
containerTable.ChatWindow.ChatTypes.ClassicChatEnabled = false
|
||||
SetCoreGuiChatConnections(containerTable)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,673 @@
|
||||
-- // FileName: ChatWindow.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Main GUI window piece. Manages ChatBar, ChannelsBar, and ChatChannels.
|
||||
|
||||
local module = {}
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local Chat = game:GetService("Chat")
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
local PlayerGui = LocalPlayer:WaitForChild("PlayerGui")
|
||||
|
||||
local PHONE_SCREEN_WIDTH = 640
|
||||
local TABLET_SCREEN_WIDTH = 1024
|
||||
|
||||
local DEVICE_PHONE = 1
|
||||
local DEVICE_TABLET = 2
|
||||
local DEVICE_DESKTOP = 3
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
local moduleChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function getClassicChatEnabled()
|
||||
if ChatSettings.ClassicChatEnabled ~= nil then
|
||||
return ChatSettings.ClassicChatEnabled
|
||||
end
|
||||
return Players.ClassicChat
|
||||
end
|
||||
|
||||
function getBubbleChatEnabled()
|
||||
if ChatSettings.BubbleChatEnabled ~= nil then
|
||||
return ChatSettings.BubbleChatEnabled
|
||||
end
|
||||
return Players.BubbleChat
|
||||
end
|
||||
|
||||
function bubbleChatOnly()
|
||||
return not getClassicChatEnabled() and getBubbleChatEnabled()
|
||||
end
|
||||
|
||||
-- only merge property defined on target
|
||||
function mergeProps(source, target)
|
||||
if not source or not target then return end
|
||||
for prop, value in pairs(source) do
|
||||
if target[prop] ~= nil then
|
||||
target[prop] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:CreateGuiObjects(targetParent)
|
||||
local userDefinedChatWindowStyle
|
||||
pcall(function()
|
||||
userDefinedChatWindowStyle= Chat:InvokeChatCallback(Enum.ChatCallbackType.OnCreatingChatWindow, nil)
|
||||
end)
|
||||
|
||||
-- merge the userdefined settings with the ChatSettings
|
||||
mergeProps(userDefinedChatWindowStyle, ChatSettings)
|
||||
|
||||
local BaseFrame = Instance.new("Frame")
|
||||
BaseFrame.BackgroundTransparency = 1
|
||||
BaseFrame.Active = ChatSettings.WindowDraggable
|
||||
BaseFrame.Parent = targetParent
|
||||
BaseFrame.AutoLocalize = false
|
||||
|
||||
local ChatBarParentFrame = Instance.new("Frame")
|
||||
ChatBarParentFrame.Selectable = false
|
||||
ChatBarParentFrame.Name = "ChatBarParentFrame"
|
||||
ChatBarParentFrame.BackgroundTransparency = 1
|
||||
ChatBarParentFrame.Parent = BaseFrame
|
||||
|
||||
local ChannelsBarParentFrame = Instance.new("Frame")
|
||||
ChannelsBarParentFrame.Selectable = false
|
||||
ChannelsBarParentFrame.Name = "ChannelsBarParentFrame"
|
||||
ChannelsBarParentFrame.BackgroundTransparency = 1
|
||||
ChannelsBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
ChannelsBarParentFrame.Parent = BaseFrame
|
||||
|
||||
local ChatChannelParentFrame = Instance.new("Frame")
|
||||
ChatChannelParentFrame.Selectable = false
|
||||
ChatChannelParentFrame.Name = "ChatChannelParentFrame"
|
||||
ChatChannelParentFrame.BackgroundTransparency = 1
|
||||
ChatChannelParentFrame.BackgroundColor3 = ChatSettings.BackGroundColor
|
||||
ChatChannelParentFrame.BackgroundTransparency = 0.6
|
||||
ChatChannelParentFrame.BorderSizePixel = 0
|
||||
ChatChannelParentFrame.Parent = BaseFrame
|
||||
|
||||
local ChatResizerFrame = Instance.new("ImageButton")
|
||||
ChatResizerFrame.Selectable = false
|
||||
ChatResizerFrame.Image = ""
|
||||
ChatResizerFrame.BackgroundTransparency = 0.6
|
||||
ChatResizerFrame.BorderSizePixel = 0
|
||||
ChatResizerFrame.Visible = false
|
||||
ChatResizerFrame.BackgroundColor3 = ChatSettings.BackGroundColor
|
||||
ChatResizerFrame.Active = true
|
||||
if bubbleChatOnly() then
|
||||
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
|
||||
else
|
||||
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
|
||||
end
|
||||
ChatResizerFrame.Parent = BaseFrame
|
||||
|
||||
local ResizeIcon = Instance.new("ImageLabel")
|
||||
ResizeIcon.Selectable = false
|
||||
ResizeIcon.Size = UDim2.new(0.8, 0, 0.8, 0)
|
||||
ResizeIcon.Position = UDim2.new(0.2, 0, 0.2, 0)
|
||||
ResizeIcon.BackgroundTransparency = 1
|
||||
ResizeIcon.Image = "rbxassetid://261880743"
|
||||
ResizeIcon.Parent = ChatResizerFrame
|
||||
|
||||
local function GetScreenGuiParent()
|
||||
--// Travel up parent list until you find the ScreenGui that the chat window is parented to
|
||||
local screenGuiParent = BaseFrame
|
||||
while (screenGuiParent and not screenGuiParent:IsA("ScreenGui")) do
|
||||
screenGuiParent = screenGuiParent.Parent
|
||||
end
|
||||
|
||||
return screenGuiParent
|
||||
end
|
||||
|
||||
|
||||
local deviceType = DEVICE_DESKTOP
|
||||
|
||||
local screenGuiParent = GetScreenGuiParent()
|
||||
if (screenGuiParent.AbsoluteSize.X <= PHONE_SCREEN_WIDTH) then
|
||||
deviceType = DEVICE_PHONE
|
||||
|
||||
elseif (screenGuiParent.AbsoluteSize.X <= TABLET_SCREEN_WIDTH) then
|
||||
deviceType = DEVICE_TABLET
|
||||
|
||||
end
|
||||
|
||||
local checkSizeLock = false
|
||||
local function doCheckSizeBounds()
|
||||
if (checkSizeLock) then return end
|
||||
checkSizeLock = true
|
||||
|
||||
if (not BaseFrame:IsDescendantOf(PlayerGui)) then return end
|
||||
|
||||
local screenGuiParent = GetScreenGuiParent()
|
||||
|
||||
local minWinSize = ChatSettings.MinimumWindowSize
|
||||
local maxWinSize = ChatSettings.MaximumWindowSize
|
||||
|
||||
local forceMinY = ChannelsBarParentFrame.AbsoluteSize.Y + ChatBarParentFrame.AbsoluteSize.Y
|
||||
|
||||
local minSizePixelX = (minWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + minWinSize.X.Offset
|
||||
local minSizePixelY = math.max((minWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + minWinSize.Y.Offset, forceMinY)
|
||||
|
||||
local maxSizePixelX = (maxWinSize.X.Scale * screenGuiParent.AbsoluteSize.X) + maxWinSize.X.Offset
|
||||
local maxSizePixelY = (maxWinSize.Y.Scale * screenGuiParent.AbsoluteSize.Y) + maxWinSize.Y.Offset
|
||||
|
||||
local absSizeX = BaseFrame.AbsoluteSize.X
|
||||
local absSizeY = BaseFrame.AbsoluteSize.Y
|
||||
|
||||
if (absSizeX < minSizePixelX) then
|
||||
local offset = UDim2.new(0, minSizePixelX - absSizeX, 0, 0)
|
||||
BaseFrame.Size = BaseFrame.Size + offset
|
||||
|
||||
elseif (absSizeX > maxSizePixelX) then
|
||||
local offset = UDim2.new(0, maxSizePixelX - absSizeX, 0, 0)
|
||||
BaseFrame.Size = BaseFrame.Size + offset
|
||||
|
||||
end
|
||||
|
||||
if (absSizeY < minSizePixelY) then
|
||||
local offset = UDim2.new(0, 0, 0, minSizePixelY - absSizeY)
|
||||
BaseFrame.Size = BaseFrame.Size + offset
|
||||
|
||||
elseif (absSizeY > maxSizePixelY) then
|
||||
local offset = UDim2.new(0, 0, 0, maxSizePixelY - absSizeY)
|
||||
BaseFrame.Size = BaseFrame.Size + offset
|
||||
|
||||
end
|
||||
|
||||
local xScale = BaseFrame.AbsoluteSize.X / screenGuiParent.AbsoluteSize.X
|
||||
local yScale = BaseFrame.AbsoluteSize.Y / screenGuiParent.AbsoluteSize.Y
|
||||
BaseFrame.Size = UDim2.new(xScale, 0, yScale, 0)
|
||||
|
||||
checkSizeLock = false
|
||||
end
|
||||
|
||||
|
||||
BaseFrame.Changed:connect(function(prop)
|
||||
if (prop == "AbsoluteSize") then
|
||||
doCheckSizeBounds()
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
|
||||
ChatResizerFrame.DragBegin:connect(function(startUdim)
|
||||
BaseFrame.Draggable = false
|
||||
end)
|
||||
|
||||
local function UpdatePositionFromDrag(atPos)
|
||||
if ChatSettings.WindowDraggable == false and ChatSettings.WindowResizable == false then
|
||||
return
|
||||
end
|
||||
local newSize = atPos - BaseFrame.AbsolutePosition + ChatResizerFrame.AbsoluteSize
|
||||
BaseFrame.Size = UDim2.new(0, newSize.X, 0, newSize.Y)
|
||||
if bubbleChatOnly() then
|
||||
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 0, 0)
|
||||
else
|
||||
ChatResizerFrame.Position = UDim2.new(1, -ChatResizerFrame.AbsoluteSize.X, 1, -ChatResizerFrame.AbsoluteSize.Y)
|
||||
end
|
||||
end
|
||||
|
||||
ChatResizerFrame.DragStopped:connect(function(endX, endY)
|
||||
BaseFrame.Draggable = ChatSettings.WindowDraggable
|
||||
--UpdatePositionFromDrag(Vector2.new(endX, endY))
|
||||
end)
|
||||
|
||||
local resizeLock = false
|
||||
ChatResizerFrame.Changed:connect(function(prop)
|
||||
if (prop == "AbsolutePosition" and not BaseFrame.Draggable) then
|
||||
if (resizeLock) then return end
|
||||
resizeLock = true
|
||||
|
||||
UpdatePositionFromDrag(ChatResizerFrame.AbsolutePosition)
|
||||
|
||||
resizeLock = false
|
||||
end
|
||||
end)
|
||||
|
||||
local function CalculateChannelsBarPixelSize(textSize)
|
||||
if (deviceType == DEVICE_PHONE) then
|
||||
textSize = textSize or ChatSettings.ChatChannelsTabTextSizePhone
|
||||
else
|
||||
textSize = textSize or ChatSettings.ChatChannelsTabTextSize
|
||||
end
|
||||
|
||||
local channelsBarTextYSize = textSize
|
||||
local chatChannelYSize = math.max(32, channelsBarTextYSize + 8) + 2
|
||||
|
||||
return chatChannelYSize
|
||||
end
|
||||
|
||||
local function CalculateChatBarPixelSize(textSize)
|
||||
if (deviceType == DEVICE_PHONE) then
|
||||
textSize = textSize or ChatSettings.ChatBarTextSizePhone
|
||||
else
|
||||
textSize = textSize or ChatSettings.ChatBarTextSize
|
||||
end
|
||||
|
||||
local chatBarTextSizeY = textSize
|
||||
local chatBarYSize = chatBarTextSizeY + (7 * 2) + (5 * 2)
|
||||
|
||||
return chatBarYSize
|
||||
end
|
||||
|
||||
if bubbleChatOnly() then
|
||||
ChatBarParentFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
ChannelsBarParentFrame.Visible = false
|
||||
ChannelsBarParentFrame.Active = false
|
||||
ChatChannelParentFrame.Visible = false
|
||||
ChatChannelParentFrame.Active = false
|
||||
|
||||
local useXScale = 0
|
||||
local useXOffset = 0
|
||||
|
||||
local screenGuiParent = GetScreenGuiParent()
|
||||
|
||||
if (deviceType == DEVICE_PHONE) then
|
||||
useXScale = ChatSettings.DefaultWindowSizePhone.X.Scale
|
||||
useXOffset = ChatSettings.DefaultWindowSizePhone.X.Offset
|
||||
|
||||
elseif (deviceType == DEVICE_TABLET) then
|
||||
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
|
||||
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
|
||||
|
||||
else
|
||||
useXScale = ChatSettings.DefaultWindowSizeTablet.X.Scale
|
||||
useXOffset = ChatSettings.DefaultWindowSizeTablet.X.Offset
|
||||
|
||||
end
|
||||
|
||||
local chatBarYSize = CalculateChatBarPixelSize()
|
||||
|
||||
BaseFrame.Size = UDim2.new(useXScale, useXOffset, 0, chatBarYSize)
|
||||
BaseFrame.Position = ChatSettings.DefaultWindowPosition
|
||||
|
||||
else
|
||||
|
||||
local screenGuiParent = GetScreenGuiParent()
|
||||
|
||||
if (deviceType == DEVICE_PHONE) then
|
||||
BaseFrame.Size = ChatSettings.DefaultWindowSizePhone
|
||||
|
||||
elseif (deviceType == DEVICE_TABLET) then
|
||||
BaseFrame.Size = ChatSettings.DefaultWindowSizeTablet
|
||||
|
||||
else
|
||||
BaseFrame.Size = ChatSettings.DefaultWindowSizeDesktop
|
||||
|
||||
end
|
||||
|
||||
BaseFrame.Position = ChatSettings.DefaultWindowPosition
|
||||
|
||||
end
|
||||
|
||||
if (deviceType == DEVICE_PHONE) then
|
||||
ChatSettings.ChatWindowTextSize = ChatSettings.ChatWindowTextSizePhone
|
||||
ChatSettings.ChatChannelsTabTextSize = ChatSettings.ChatChannelsTabTextSizePhone
|
||||
ChatSettings.ChatBarTextSize = ChatSettings.ChatBarTextSizePhone
|
||||
end
|
||||
|
||||
local function UpdateDraggable(enabled)
|
||||
BaseFrame.Active = enabled
|
||||
BaseFrame.Draggable = enabled
|
||||
end
|
||||
|
||||
local function UpdateResizable(enabled)
|
||||
ChatResizerFrame.Visible = enabled
|
||||
ChatResizerFrame.Draggable = enabled
|
||||
|
||||
local frameSizeY = ChatBarParentFrame.Size.Y.Offset
|
||||
|
||||
if (enabled) then
|
||||
ChatBarParentFrame.Size = UDim2.new(1, -frameSizeY - 2, 0, frameSizeY)
|
||||
if not bubbleChatOnly() then
|
||||
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
|
||||
end
|
||||
else
|
||||
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, frameSizeY)
|
||||
if not bubbleChatOnly() then
|
||||
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -frameSizeY)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateChatChannelParentFrameSize()
|
||||
local channelsBarSize = CalculateChannelsBarPixelSize()
|
||||
local chatBarSize = CalculateChatBarPixelSize()
|
||||
|
||||
if (ChatSettings.ShowChannelsBar) then
|
||||
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(channelsBarSize + chatBarSize + 2 + 2))
|
||||
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, channelsBarSize + 2)
|
||||
|
||||
else
|
||||
ChatChannelParentFrame.Size = UDim2.new(1, 0, 1, -(chatBarSize + 2 + 2))
|
||||
ChatChannelParentFrame.Position = UDim2.new(0, 0, 0, 2)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateChatChannelsTabTextSize(size)
|
||||
local channelsBarSize = CalculateChannelsBarPixelSize(size)
|
||||
ChannelsBarParentFrame.Size = UDim2.new(1, 0, 0, channelsBarSize)
|
||||
|
||||
UpdateChatChannelParentFrameSize()
|
||||
end
|
||||
|
||||
local function UpdateChatBarTextSize(size)
|
||||
local chatBarSize = CalculateChatBarPixelSize(size)
|
||||
|
||||
ChatBarParentFrame.Size = UDim2.new(1, 0, 0, chatBarSize)
|
||||
if not bubbleChatOnly() then
|
||||
ChatBarParentFrame.Position = UDim2.new(0, 0, 1, -chatBarSize)
|
||||
end
|
||||
|
||||
ChatResizerFrame.Size = UDim2.new(0, chatBarSize, 0, chatBarSize)
|
||||
ChatResizerFrame.Position = UDim2.new(1, -chatBarSize, 1, -chatBarSize)
|
||||
|
||||
UpdateChatChannelParentFrameSize()
|
||||
UpdateResizable(ChatSettings.WindowResizable)
|
||||
end
|
||||
|
||||
local function UpdateShowChannelsBar(enabled)
|
||||
ChannelsBarParentFrame.Visible = enabled
|
||||
UpdateChatChannelParentFrameSize()
|
||||
end
|
||||
|
||||
UpdateChatChannelsTabTextSize(ChatSettings.ChatChannelsTabTextSize)
|
||||
UpdateChatBarTextSize(ChatSettings.ChatBarTextSize)
|
||||
UpdateDraggable(ChatSettings.WindowDraggable)
|
||||
UpdateResizable(ChatSettings.WindowResizable)
|
||||
UpdateShowChannelsBar(ChatSettings.ShowChannelsBar)
|
||||
|
||||
ChatSettings.SettingsChanged:connect(function(setting, value)
|
||||
if (setting == "WindowDraggable") then
|
||||
UpdateDraggable(value)
|
||||
|
||||
elseif (setting == "WindowResizable") then
|
||||
UpdateResizable(value)
|
||||
|
||||
elseif (setting == "ChatChannelsTabTextSize") then
|
||||
UpdateChatChannelsTabTextSize(value)
|
||||
|
||||
elseif (setting == "ChatBarTextSize") then
|
||||
UpdateChatBarTextSize(value)
|
||||
|
||||
elseif (setting == "ShowChannelsBar") then
|
||||
UpdateShowChannelsBar(value)
|
||||
|
||||
end
|
||||
end)
|
||||
|
||||
self.GuiObject = BaseFrame
|
||||
|
||||
self.GuiObjects.BaseFrame = BaseFrame
|
||||
self.GuiObjects.ChatBarParentFrame = ChatBarParentFrame
|
||||
self.GuiObjects.ChannelsBarParentFrame = ChannelsBarParentFrame
|
||||
self.GuiObjects.ChatChannelParentFrame = ChatChannelParentFrame
|
||||
self.GuiObjects.ChatResizerFrame = ChatResizerFrame
|
||||
self.GuiObjects.ResizeIcon = ResizeIcon
|
||||
self:AnimGuiObjects()
|
||||
end
|
||||
|
||||
function methods:GetChatBar()
|
||||
return self.ChatBar
|
||||
end
|
||||
|
||||
function methods:RegisterChatBar(ChatBar)
|
||||
self.ChatBar = ChatBar
|
||||
self.ChatBar:CreateGuiObjects(self.GuiObjects.ChatBarParentFrame)
|
||||
end
|
||||
|
||||
function methods:RegisterChannelsBar(ChannelsBar)
|
||||
self.ChannelsBar = ChannelsBar
|
||||
self.ChannelsBar:CreateGuiObjects(self.GuiObjects.ChannelsBarParentFrame)
|
||||
end
|
||||
|
||||
function methods:RegisterMessageLogDisplay(MessageLogDisplay)
|
||||
self.MessageLogDisplay = MessageLogDisplay
|
||||
self.MessageLogDisplay.GuiObject.Parent = self.GuiObjects.ChatChannelParentFrame
|
||||
end
|
||||
|
||||
function methods:AddChannel(channelName)
|
||||
if (self:GetChannel(channelName)) then
|
||||
error("Channel '" .. channelName .. "' already exists!")
|
||||
return
|
||||
end
|
||||
|
||||
local channel = moduleChatChannel.new(channelName, self.MessageLogDisplay)
|
||||
self.Channels[channelName:lower()] = channel
|
||||
|
||||
channel:SetActive(false)
|
||||
|
||||
local tab = self.ChannelsBar:AddChannelTab(channelName)
|
||||
tab.NameTag.MouseButton1Click:connect(function()
|
||||
self:SwitchCurrentChannel(channelName)
|
||||
end)
|
||||
|
||||
channel:RegisterChannelTab(tab)
|
||||
|
||||
return channel
|
||||
end
|
||||
|
||||
function methods:GetFirstChannel()
|
||||
--// Channels are not indexed numerically, so this function is necessary.
|
||||
--// Grabs and returns the first channel it happens to, or nil if none exist.
|
||||
for i, v in pairs(self.Channels) do
|
||||
return v
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function methods:RemoveChannel(channelName)
|
||||
if (not self:GetChannel(channelName)) then
|
||||
error("Channel '" .. channelName .. "' does not exist!")
|
||||
end
|
||||
|
||||
local indexName = channelName:lower()
|
||||
|
||||
local needsChannelSwitch = false
|
||||
if (self.Channels[indexName] == self:GetCurrentChannel()) then
|
||||
needsChannelSwitch = true
|
||||
|
||||
self:SwitchCurrentChannel(nil)
|
||||
end
|
||||
|
||||
self.Channels[indexName]:Destroy()
|
||||
self.Channels[indexName] = nil
|
||||
|
||||
self.ChannelsBar:RemoveChannelTab(channelName)
|
||||
|
||||
if (needsChannelSwitch) then
|
||||
local generalChannelExists = (self:GetChannel(ChatSettings.GeneralChannelName) ~= nil)
|
||||
local removingGeneralChannel = (indexName == ChatSettings.GeneralChannelName:lower())
|
||||
|
||||
local targetSwitchChannel = nil
|
||||
|
||||
if (generalChannelExists and not removingGeneralChannel) then
|
||||
targetSwitchChannel = ChatSettings.GeneralChannelName
|
||||
else
|
||||
local firstChannel = self:GetFirstChannel()
|
||||
targetSwitchChannel = (firstChannel and firstChannel.Name or nil)
|
||||
end
|
||||
|
||||
self:SwitchCurrentChannel(targetSwitchChannel)
|
||||
end
|
||||
|
||||
if not ChatSettings.ShowChannelsBar then
|
||||
if self.ChatBar.TargetChannel == channelName then
|
||||
self.ChatBar:SetChannelTarget(ChatSettings.GeneralChannelName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetChannel(channelName)
|
||||
return channelName and self.Channels[channelName:lower()] or nil
|
||||
end
|
||||
|
||||
function methods:GetTargetMessageChannel()
|
||||
if (not ChatSettings.ShowChannelsBar) then
|
||||
return self.ChatBar.TargetChannel
|
||||
else
|
||||
local curChannel = self:GetCurrentChannel()
|
||||
return curChannel and curChannel.Name
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetCurrentChannel()
|
||||
return self.CurrentChannel
|
||||
end
|
||||
|
||||
function methods:SwitchCurrentChannel(channelName)
|
||||
if (not ChatSettings.ShowChannelsBar) then
|
||||
local targ = self:GetChannel(channelName)
|
||||
if (targ) then
|
||||
self.ChatBar:SetChannelTarget(targ.Name)
|
||||
end
|
||||
|
||||
channelName = ChatSettings.GeneralChannelName
|
||||
end
|
||||
|
||||
local cur = self:GetCurrentChannel()
|
||||
local new = self:GetChannel(channelName)
|
||||
if new == nil then
|
||||
error(string.format("Channel '%s' does not exist.", channelName))
|
||||
end
|
||||
|
||||
if (new ~= cur) then
|
||||
if (cur) then
|
||||
cur:SetActive(false)
|
||||
local tab = self.ChannelsBar:GetChannelTab(cur.Name)
|
||||
tab:SetActive(false)
|
||||
end
|
||||
|
||||
if (new) then
|
||||
new:SetActive(true)
|
||||
local tab = self.ChannelsBar:GetChannelTab(new.Name)
|
||||
tab:SetActive(true)
|
||||
end
|
||||
|
||||
self.CurrentChannel = new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function methods:UpdateFrameVisibility()
|
||||
self.GuiObject.Visible = (self.Visible and self.CoreGuiEnabled)
|
||||
end
|
||||
|
||||
function methods:GetVisible()
|
||||
return self.Visible
|
||||
end
|
||||
|
||||
function methods:SetVisible(visible)
|
||||
self.Visible = visible
|
||||
self:UpdateFrameVisibility()
|
||||
end
|
||||
|
||||
function methods:GetCoreGuiEnabled()
|
||||
return self.CoreGuiEnabled
|
||||
end
|
||||
|
||||
function methods:SetCoreGuiEnabled(enabled)
|
||||
self.CoreGuiEnabled = enabled
|
||||
self:UpdateFrameVisibility()
|
||||
end
|
||||
|
||||
function methods:EnableResizable()
|
||||
self.GuiObjects.ChatResizerFrame.Active = true
|
||||
end
|
||||
|
||||
function methods:DisableResizable()
|
||||
self.GuiObjects.ChatResizerFrame.Active = false
|
||||
end
|
||||
|
||||
function methods:FadeOutBackground(duration)
|
||||
self.ChannelsBar:FadeOutBackground(duration)
|
||||
self.MessageLogDisplay:FadeOutBackground(duration)
|
||||
self.ChatBar:FadeOutBackground(duration)
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 1
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInBackground(duration)
|
||||
self.ChannelsBar:FadeInBackground(duration)
|
||||
self.MessageLogDisplay:FadeInBackground(duration)
|
||||
self.ChatBar:FadeInBackground(duration)
|
||||
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
|
||||
function methods:FadeOutText(duration)
|
||||
self.MessageLogDisplay:FadeOutText(duration)
|
||||
self.ChannelsBar:FadeOutText(duration)
|
||||
end
|
||||
|
||||
function methods:FadeInText(duration)
|
||||
self.MessageLogDisplay:FadeInText(duration)
|
||||
self.ChannelsBar:FadeInText(duration)
|
||||
end
|
||||
|
||||
function methods:AnimGuiObjects()
|
||||
self.GuiObjects.ChatChannelParentFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.ChatResizerFrame.BackgroundTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
self.GuiObjects.ResizeIcon.ImageTransparency = self.AnimParams.Background_CurrentTransparency
|
||||
end
|
||||
|
||||
function methods:InitializeAnimParams()
|
||||
self.AnimParams.Background_TargetTransparency = 0.6
|
||||
self.AnimParams.Background_CurrentTransparency = 0.6
|
||||
self.AnimParams.Background_NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(0)
|
||||
end
|
||||
|
||||
function methods:Update(dtScale)
|
||||
self.ChatBar:Update(dtScale)
|
||||
self.ChannelsBar:Update(dtScale)
|
||||
self.MessageLogDisplay:Update(dtScale)
|
||||
|
||||
self.AnimParams.Background_CurrentTransparency = CurveUtil:Expt(
|
||||
self.AnimParams.Background_CurrentTransparency,
|
||||
self.AnimParams.Background_TargetTransparency,
|
||||
self.AnimParams.Background_NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
|
||||
self:AnimGuiObjects()
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.GuiObject = nil
|
||||
obj.GuiObjects = {}
|
||||
|
||||
obj.ChatBar = nil
|
||||
obj.ChannelsBar = nil
|
||||
obj.MessageLogDisplay = nil
|
||||
|
||||
obj.Channels = {}
|
||||
obj.CurrentChannel = nil
|
||||
|
||||
obj.Visible = true
|
||||
obj.CoreGuiEnabled = true
|
||||
|
||||
obj.AnimParams = {}
|
||||
|
||||
obj:InitializeAnimParams()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
local runnerScriptName = "ChatScript"
|
||||
local bubbleChatScriptName = "BubbleChat"
|
||||
local installDirectory = game:GetService("Chat")
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
local StarterPlayerScripts = game:GetService("StarterPlayer"):WaitForChild("StarterPlayerScripts")
|
||||
|
||||
local function LoadLocalScript(location, name, parent)
|
||||
local originalModule = location:WaitForChild(name)
|
||||
local script = Instance.new("LocalScript")
|
||||
script.Name = name
|
||||
script.Source = originalModule.Source
|
||||
script.Parent = parent
|
||||
return script
|
||||
end
|
||||
|
||||
local function LoadModule(location, name, parent)
|
||||
local originalModule = location:WaitForChild(name)
|
||||
local module = Instance.new("ModuleScript")
|
||||
module.Name = name
|
||||
module.Source = originalModule.Source
|
||||
module.Parent = parent
|
||||
return module
|
||||
end
|
||||
|
||||
local function GetBoolValue(parent, name, defaultValue)
|
||||
local boolValue = parent:FindFirstChild(name)
|
||||
if boolValue then
|
||||
if boolValue:IsA("BoolValue") then
|
||||
return boolValue.Value
|
||||
end
|
||||
end
|
||||
return defaultValue
|
||||
end
|
||||
|
||||
local function Install()
|
||||
local chatScriptArchivable = true
|
||||
local ChatScript = installDirectory:FindFirstChild(runnerScriptName)
|
||||
if not ChatScript then
|
||||
chatScriptArchivable = false
|
||||
ChatScript = LoadLocalScript(script.Parent, runnerScriptName, installDirectory)
|
||||
local ChatMain = LoadModule(script.Parent, "ChatMain", ChatScript)
|
||||
|
||||
LoadModule(script.Parent, "ChannelsBar", ChatMain)
|
||||
LoadModule(script.Parent, "ChatBar", ChatMain)
|
||||
LoadModule(script.Parent, "ChatChannel", ChatMain)
|
||||
LoadModule(script.Parent, "MessageLogDisplay", ChatMain)
|
||||
LoadModule(script.Parent, "ChatWindow", ChatMain)
|
||||
LoadModule(script.Parent, "MessageLabelCreator", ChatMain)
|
||||
LoadModule(script.Parent, "CommandProcessor", ChatMain)
|
||||
LoadModule(script.Parent, "ChannelsTab", ChatMain)
|
||||
LoadModule(script.Parent.Parent.Parent.Common, "ObjectPool", ChatMain)
|
||||
LoadModule(script.Parent, "MessageSender", ChatMain)
|
||||
LoadModule(script.Parent, "CurveUtil", ChatMain)
|
||||
end
|
||||
|
||||
local bubbleChatScriptArchivable = true
|
||||
local BubbleChatScript = installDirectory:FindFirstChild(bubbleChatScriptName)
|
||||
if not BubbleChatScript then
|
||||
bubbleChatScriptArchivable = false
|
||||
BubbleChatScript = LoadLocalScript(script.Parent.BubbleChat, bubbleChatScriptName, installDirectory)
|
||||
end
|
||||
|
||||
local clientChatModules = installDirectory:FindFirstChild("ClientChatModules")
|
||||
if not clientChatModules then
|
||||
clientChatModules = Instance.new("Folder")
|
||||
clientChatModules.Name = "ClientChatModules"
|
||||
clientChatModules.Archivable = false
|
||||
|
||||
clientChatModules.Parent = installDirectory
|
||||
end
|
||||
|
||||
local chatSettings = clientChatModules:FindFirstChild("ChatSettings")
|
||||
if not chatSettings then
|
||||
LoadModule(script.Parent.DefaultClientChatModules, "ChatSettings", clientChatModules)
|
||||
end
|
||||
|
||||
local chatConstants = clientChatModules:FindFirstChild("ChatConstants")
|
||||
if not chatConstants then
|
||||
LoadModule(script.Parent.DefaultClientChatModules, "ChatConstants", clientChatModules)
|
||||
end
|
||||
|
||||
local ChatLocalization = clientChatModules:FindFirstChild("ChatLocalization")
|
||||
if not ChatLocalization then
|
||||
LoadModule(script.Parent.DefaultClientChatModules, "ChatLocalization", clientChatModules)
|
||||
end
|
||||
|
||||
local MessageCreatorModules = clientChatModules:FindFirstChild("MessageCreatorModules")
|
||||
if not MessageCreatorModules then
|
||||
MessageCreatorModules = Instance.new("Folder")
|
||||
MessageCreatorModules.Name = "MessageCreatorModules"
|
||||
MessageCreatorModules.Archivable = false
|
||||
|
||||
local InsertDefaults = Instance.new("BoolValue")
|
||||
InsertDefaults.Name = "InsertDefaultModules"
|
||||
InsertDefaults.Value = true
|
||||
InsertDefaults.Parent = MessageCreatorModules
|
||||
|
||||
MessageCreatorModules.Parent = clientChatModules
|
||||
end
|
||||
|
||||
local insertDefaultMessageCreators = GetBoolValue(MessageCreatorModules, "InsertDefaultModules", false)
|
||||
|
||||
if insertDefaultMessageCreators then
|
||||
local creatorModules = script.Parent.DefaultClientChatModules.MessageCreatorModules:GetChildren()
|
||||
for i = 1, #creatorModules do
|
||||
if not MessageCreatorModules:FindFirstChild(creatorModules[i].Name) then
|
||||
LoadModule(script.Parent.DefaultClientChatModules.MessageCreatorModules, creatorModules[i].Name, MessageCreatorModules)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local CommandModules = clientChatModules:FindFirstChild("CommandModules")
|
||||
if not CommandModules then
|
||||
CommandModules = Instance.new("Folder")
|
||||
CommandModules.Name = "CommandModules"
|
||||
CommandModules.Archivable = false
|
||||
|
||||
local InsertDefaults = Instance.new("BoolValue")
|
||||
InsertDefaults.Name = "InsertDefaultModules"
|
||||
InsertDefaults.Value = true
|
||||
InsertDefaults.Parent = CommandModules
|
||||
|
||||
CommandModules.Parent = clientChatModules
|
||||
end
|
||||
|
||||
local insertDefaultCommands = GetBoolValue(CommandModules, "InsertDefaultModules", false)
|
||||
|
||||
if insertDefaultCommands then
|
||||
local commandModules = script.Parent.DefaultClientChatModules.CommandModules:GetChildren()
|
||||
for i = 1, #commandModules do
|
||||
if not CommandModules:FindFirstChild(commandModules[i].Name) then
|
||||
LoadModule(script.Parent.DefaultClientChatModules.CommandModules, commandModules[i].Name, CommandModules)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not StarterPlayerScripts:FindFirstChild(runnerScriptName) then
|
||||
local ChatScriptCopy = ChatScript:Clone()
|
||||
ChatScriptCopy.Parent = StarterPlayerScripts
|
||||
ChatScriptCopy.Archivable = false
|
||||
|
||||
local currentPlayers = PlayersService:GetPlayers()
|
||||
for i, player in pairs(currentPlayers) do
|
||||
if (player:IsA("Player") and player:FindFirstChild("PlayerScripts") and not player.PlayerScripts:FindFirstChild(runnerScriptName)) then
|
||||
ChatScriptCopy = ChatScript:Clone()
|
||||
ChatScriptCopy.Parent = player.PlayerScripts
|
||||
ChatScriptCopy.Archivable = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatScript.Archivable = chatScriptArchivable
|
||||
|
||||
if not StarterPlayerScripts:FindFirstChild(bubbleChatScriptName) then
|
||||
local BubbleChatScriptCopy = BubbleChatScript:Clone()
|
||||
BubbleChatScriptCopy.Parent = StarterPlayerScripts
|
||||
BubbleChatScriptCopy.Archivable = false
|
||||
|
||||
local currentPlayers = PlayersService:GetPlayers()
|
||||
for i, player in pairs(currentPlayers) do
|
||||
if (player:IsA("Player") and player:FindFirstChild("PlayerScripts") and not player.PlayerScripts:FindFirstChild(bubbleChatScriptName)) then
|
||||
BubbleChatScriptCopy = BubbleChatScript:Clone()
|
||||
BubbleChatScriptCopy.Parent = player.PlayerScripts
|
||||
BubbleChatScriptCopy.Archivable = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BubbleChatScript.Archivable = bubbleChatScriptArchivable
|
||||
end
|
||||
|
||||
return Install
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
-- // FileName: ProcessCommands.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Module for processing commands using the client CommandModules
|
||||
|
||||
local module = {}
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local commandModules = clientChatModules:WaitForChild("CommandModules")
|
||||
local commandUtil = require(commandModules:WaitForChild("Util"))
|
||||
local modulesFolder = script.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
|
||||
function methods:SetupCommandProcessors()
|
||||
local commands = commandModules:GetChildren()
|
||||
for i = 1, #commands do
|
||||
if commands[i]:IsA("ModuleScript") then
|
||||
if commands[i].Name ~= "Util" then
|
||||
local commandProcessor = require(commands[i])
|
||||
local processorType = commandProcessor[commandUtil.KEY_COMMAND_PROCESSOR_TYPE]
|
||||
local processorFunction = commandProcessor[commandUtil.KEY_PROCESSOR_FUNCTION]
|
||||
if processorType == commandUtil.IN_PROGRESS_MESSAGE_PROCESSOR then
|
||||
table.insert(self.InProgressMessageProcessors, processorFunction)
|
||||
elseif processorType == commandUtil.COMPLETED_MESSAGE_PROCESSOR then
|
||||
table.insert(self.CompletedMessageProcessors, processorFunction)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ProcessCompletedChatMessage(message, ChatWindow)
|
||||
for i = 1, #self.CompletedMessageProcessors do
|
||||
local processedCommand = self.CompletedMessageProcessors[i](message, ChatWindow, ChatSettings)
|
||||
if processedCommand then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function methods:ProcessInProgressChatMessage(message, ChatWindow, ChatBar)
|
||||
for i = 1, #self.InProgressMessageProcessors do
|
||||
local customState = self.InProgressMessageProcessors[i](message, ChatWindow, ChatBar, ChatSettings)
|
||||
if customState then
|
||||
return customState
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.CompletedMessageProcessors = {}
|
||||
obj.InProgressMessageProcessors = {}
|
||||
|
||||
obj:SetupCommandProcessors()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,78 @@
|
||||
local CurveUtil = { }
|
||||
local DEFAULT_THRESHOLD = 0.01
|
||||
|
||||
function CurveUtil:Expt(start, to, pct, dt_scale)
|
||||
if math.abs(to - start) < DEFAULT_THRESHOLD then
|
||||
return to
|
||||
end
|
||||
|
||||
local y = CurveUtil:Expty(start,to,pct,dt_scale)
|
||||
|
||||
--rtv = start + (to - start) * timescaled_friction--
|
||||
local delta = (to - start) * y
|
||||
return start + delta
|
||||
end
|
||||
|
||||
function CurveUtil:Expty(start, to, pct, dt_scale)
|
||||
--y = e ^ (-a * timescale)--
|
||||
local friction = 1 - pct
|
||||
local a = -math.log(friction)
|
||||
return 1 - math.exp(-a * dt_scale)
|
||||
end
|
||||
|
||||
function CurveUtil:Sign(val)
|
||||
if val > 0 then
|
||||
return 1
|
||||
elseif val < 0 then
|
||||
return -1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function CurveUtil:BezierValForT(p0, p1, p2, p3, t)
|
||||
local cp0 = (1 - t) * (1 - t) * (1 - t)
|
||||
local cp1 = 3 * t * (1-t)*(1-t)
|
||||
local cp2 = 3 * t * t * (1 - t)
|
||||
local cp3 = t * t * t
|
||||
return cp0 * p0 + cp1 * p1 + cp2 * p2 + cp3 * p3
|
||||
end
|
||||
|
||||
CurveUtil._BezierPt2ForT = { x = 0; y = 0 }
|
||||
function CurveUtil:BezierPt2ForT(
|
||||
p0x, p0y,
|
||||
p1x, p1y,
|
||||
p2x, p2y,
|
||||
p3x, p3y,
|
||||
t)
|
||||
|
||||
CurveUtil._BezierPt2ForT.x = CurveUtil:BezierValForT(p0x,p1x,p2x,p3x,t)
|
||||
CurveUtil._BezierPt2ForT.y = CurveUtil:BezierValForT(p0y,p1y,p2y,p3y,t)
|
||||
return CurveUtil._BezierPt2ForT
|
||||
end
|
||||
|
||||
function CurveUtil:YForPointOf2PtLine(pt1, pt2, x)
|
||||
--(y - y1)/(x - x1) = m--
|
||||
local m = (pt1.y - pt2.y) / (pt1.x - pt2.x)
|
||||
--y - mx = b--
|
||||
local b = pt1.y - m * pt1.x
|
||||
return m * x + b
|
||||
end
|
||||
|
||||
function CurveUtil:DeltaTimeToTimescale(s_frame_delta_time)
|
||||
return s_frame_delta_time / (1.0 / 60.0)
|
||||
end
|
||||
|
||||
function CurveUtil:SecondsToTick(sec)
|
||||
return (1 / 60.0) / sec
|
||||
end
|
||||
|
||||
function CurveUtil:ExptValueInSeconds(threshold, start, seconds)
|
||||
return 1 - math.pow((threshold / start), 1 / (60.0 * seconds))
|
||||
end
|
||||
|
||||
function CurveUtil:NormalizedDefaultExptValueInSeconds(seconds)
|
||||
return self:ExptValueInSeconds(DEFAULT_THRESHOLD, 1, seconds)
|
||||
end
|
||||
|
||||
return CurveUtil
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
-- // FileName: ChatConstants.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Module for creating chat constants shared between server and client.
|
||||
|
||||
local module = {}
|
||||
|
||||
---[[ Message Types ]]
|
||||
module.MessageTypeDefault = "Message"
|
||||
module.MessageTypeSystem = "System"
|
||||
module.MessageTypeMeCommand = "MeCommand"
|
||||
module.MessageTypeWelcome = "Welcome"
|
||||
module.MessageTypeSetCore = "SetCore"
|
||||
module.MessageTypeWhisper = "Whisper"
|
||||
|
||||
--[[ Version ]]
|
||||
module.MajorVersion = 0
|
||||
module.MinorVersion = 8
|
||||
module.BuildVersion = "2018.05.16"
|
||||
---[[ Command/Filter Priorities ]]
|
||||
module.VeryLowPriority = -5
|
||||
module.LowPriority = 0
|
||||
module.StandardPriority = 10
|
||||
module.HighPriority = 20
|
||||
module.VeryHighPriority = 25
|
||||
|
||||
module.WhisperChannelPrefix = "To "
|
||||
|
||||
return module
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
local ChatService = game:GetService("Chat")
|
||||
|
||||
local ChatLocalization = {
|
||||
_hasFetchedLocalization = false,
|
||||
}
|
||||
|
||||
function ChatLocalization:_getTranslator()
|
||||
if not self._translator and not self._hasFetchedLocalization then
|
||||
-- Don't keep retrying if this fails.
|
||||
self._hasFetchedLocalization = true
|
||||
|
||||
local localizationTable = ChatService:WaitForChild("ChatLocalization", 4)
|
||||
if localizationTable then
|
||||
self._translator = localizationTable:GetTranslator(LocalizationService.RobloxLocaleId)
|
||||
LocalizationService:GetPropertyChangedSignal("RobloxLocaleId"):Connect(function()
|
||||
-- If RobloxLocaleId changes invalidate the cached Translator.
|
||||
self._hasFetchedLocalization = false
|
||||
self._translator = nil
|
||||
end)
|
||||
else
|
||||
warn("Missing ChatLocalization. Chat interface will not be localized.")
|
||||
end
|
||||
end
|
||||
return self._translator
|
||||
end
|
||||
|
||||
function ChatLocalization:Get(key, default)
|
||||
local rtv = default
|
||||
pcall(function()
|
||||
local translator = self:_getTranslator()
|
||||
if translator then
|
||||
rtv = translator:FormatByKey(key)
|
||||
else
|
||||
warn("Missing Translator. Used default for", key)
|
||||
end
|
||||
end)
|
||||
return rtv
|
||||
end
|
||||
|
||||
return ChatLocalization
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
-- // FileName: ChatSettings.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Settings module for configuring different aspects of the chat window.
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
|
||||
local clientChatModules = script.Parent
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local module = {}
|
||||
|
||||
---[[ Chat Behaviour Settings ]]
|
||||
module.WindowDraggable = false
|
||||
module.WindowResizable = false
|
||||
module.ShowChannelsBar = false
|
||||
module.GamepadNavigationEnabled = false
|
||||
module.ShowUserOwnFilteredMessage = true --Show a user the filtered version of their message rather than the original.
|
||||
-- Make the chat work when the top bar is off
|
||||
module.ChatOnWithTopBarOff = false
|
||||
module.ScreenGuiDisplayOrder = 6 -- The DisplayOrder value for the ScreenGui containing the chat.
|
||||
|
||||
module.ShowFriendJoinNotification = true -- Show a notification in the chat when a players friend joins the game.
|
||||
|
||||
--- Replace with true/false to force the chat type. Otherwise this will default to the setting on the website.
|
||||
module.BubbleChatEnabled = PlayersService.BubbleChat
|
||||
module.ClassicChatEnabled = PlayersService.ClassicChat
|
||||
|
||||
---[[ Chat Text Size Settings ]]
|
||||
module.ChatWindowTextSize = 18
|
||||
module.ChatChannelsTabTextSize = 18
|
||||
module.ChatBarTextSize = 18
|
||||
module.ChatWindowTextSizePhone = 14
|
||||
module.ChatChannelsTabTextSizePhone = 18
|
||||
module.ChatBarTextSizePhone = 14
|
||||
|
||||
---[[ Font Settings ]]
|
||||
module.DefaultFont = Enum.Font.SourceSansBold
|
||||
module.ChatBarFont = Enum.Font.SourceSansBold
|
||||
|
||||
----[[ Color Settings ]]
|
||||
module.BackGroundColor = Color3.new(0, 0, 0)
|
||||
module.DefaultMessageColor = Color3.new(1, 1, 1)
|
||||
module.DefaultNameColor = Color3.new(1, 1, 1)
|
||||
module.ChatBarBackGroundColor = Color3.new(0, 0, 0)
|
||||
module.ChatBarBoxColor = Color3.new(1, 1, 1)
|
||||
module.ChatBarTextColor = Color3.new(0, 0, 0)
|
||||
module.ChannelsTabUnselectedColor = Color3.new(0, 0, 0)
|
||||
module.ChannelsTabSelectedColor = Color3.new(30/255, 30/255, 30/255)
|
||||
module.DefaultChannelNameColor = Color3.fromRGB(35, 76, 142)
|
||||
module.WhisperChannelNameColor = Color3.fromRGB(102, 14, 102)
|
||||
module.ErrorMessageTextColor = Color3.fromRGB(245, 50, 50)
|
||||
|
||||
---[[ Window Settings ]]
|
||||
module.MinimumWindowSize = UDim2.new(0.3, 0, 0.25, 0)
|
||||
module.MaximumWindowSize = UDim2.new(1, 0, 1, 0) -- if you change this to be greater than full screen size, weird things start to happen with size/position bounds checking.
|
||||
module.DefaultWindowPosition = UDim2.new(0, 0, 0, 0)
|
||||
local extraOffset = (7 * 2) + (5 * 2) -- Extra chatbar vertical offset
|
||||
module.DefaultWindowSizePhone = UDim2.new(0.5, 0, 0.5, extraOffset)
|
||||
module.DefaultWindowSizeTablet = UDim2.new(0.4, 0, 0.3, extraOffset)
|
||||
module.DefaultWindowSizeDesktop = UDim2.new(0.3, 0, 0.25, extraOffset)
|
||||
|
||||
---[[ Fade Out and In Settings ]]
|
||||
module.ChatWindowBackgroundFadeOutTime = 0.5 --Chat background will fade out after this many seconds.
|
||||
module.ChatWindowTextFadeOutTime = 30 --Chat text will fade out after this many seconds.
|
||||
module.ChatDefaultFadeDuration = 0.8
|
||||
module.ChatShouldFadeInFromNewInformation = false
|
||||
module.ChatAnimationFPS = 20.0
|
||||
|
||||
---[[ Channel Settings ]]
|
||||
module.GeneralChannelName = "All" -- You can set to nil to turn off echoing to a general channel.
|
||||
module.EchoMessagesInGeneralChannel = true -- Should messages to channels other than general be echoed into the general channel.
|
||||
-- Setting this to false should be used with ShowChannelsBar
|
||||
module.ChannelsBarFullTabSize = 4 -- number of tabs in bar before it starts to scroll
|
||||
module.MaxChannelNameLength = 12
|
||||
--// Although this feature is pretty much ready, it needs some UI design still.
|
||||
module.RightClickToLeaveChannelEnabled = false
|
||||
module.MessageHistoryLengthPerChannel = 50
|
||||
-- Show the help text for joining and leaving channels. This is not useful unless custom channels have been added.
|
||||
-- So it is turned off by default.
|
||||
module.ShowJoinAndLeaveHelpText = false
|
||||
|
||||
---[[ Message Settings ]]
|
||||
module.MaximumMessageLength = 200
|
||||
module.DisallowedWhiteSpace = {"\n", "\r", "\t", "\v", "\f"}
|
||||
module.ClickOnPlayerNameToWhisper = true
|
||||
module.ClickOnChannelNameToSetMainChannel = true
|
||||
module.BubbleChatMessageTypes = {ChatConstants.MessageTypeDefault, ChatConstants.MessageTypeWhisper}
|
||||
|
||||
---[[ Misc Settings ]]
|
||||
module.WhisperCommandAutoCompletePlayerNames = true
|
||||
|
||||
local ChangedEvent = Instance.new("BindableEvent")
|
||||
|
||||
local proxyTable = setmetatable({},
|
||||
{
|
||||
__index = function(tbl, index)
|
||||
return module[index]
|
||||
end,
|
||||
__newindex = function(tbl, index, value)
|
||||
module[index] = value
|
||||
ChangedEvent:Fire(index, value)
|
||||
end,
|
||||
})
|
||||
|
||||
rawset(proxyTable, "SettingsChanged", ChangedEvent.Event)
|
||||
|
||||
return proxyTable
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
-- // FileName: ClearMessages.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Command to clear the message log of the current channel.
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatSettings)
|
||||
if string.sub(message, 1, 4):lower() == "/cls" or string.sub(message, 1, 6):lower() == "/clear" then
|
||||
local currentChannel = ChatWindow:GetCurrentChannel()
|
||||
if (currentChannel) then
|
||||
currentChannel:ClearMessageLog()
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
-- // FileName: DeveloperConsole.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Command to open or close the developer console.
|
||||
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatSettings)
|
||||
if string.sub(message, 1, 8):lower() == "/console" or string.sub(message, 1, 11):lower() == "/newconsole" then
|
||||
local success, developerConsoleVisible = pcall(function() return StarterGui:GetCore("DevConsoleVisible") end)
|
||||
if success then
|
||||
local success, err = pcall(function() StarterGui:SetCore("DevConsoleVisible", not developerConsoleVisible) end)
|
||||
if not success and err then
|
||||
print("Error making developer console visible: " ..err)
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif string.sub(message, 1, 11):lower() == "/oldconsole" then
|
||||
local success, developerConsoleVisible = pcall(function() return StarterGui:GetCore("DeveloperConsoleVisible") end)
|
||||
if success then
|
||||
local success, err = pcall(function() StarterGui:SetCore("DeveloperConsoleVisible", not developerConsoleVisible) end)
|
||||
if not success and err then
|
||||
print("Error making developer console visible: " ..err)
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
-- // FileName: GetVersion.lua
|
||||
-- // Written by: spotco
|
||||
-- // Description: Command to print the chat version.
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
local ChatConstants = require(script.Parent.Parent:WaitForChild("ChatConstants"))
|
||||
|
||||
local function ProcessMessage(message, ChatWindow, _)
|
||||
if string.sub(message, 1, 8):lower() == "/version" or string.sub(message, 1, 9):lower() == "/version " then
|
||||
util:SendSystemMessageToSelf(
|
||||
string.format("This game is running chat version [%d.%d.%s].",
|
||||
ChatConstants.MajorVersion,
|
||||
ChatConstants.MinorVersion,
|
||||
ChatConstants.BuildVersion),
|
||||
ChatWindow:GetCurrentChannel(),
|
||||
{})
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
-- // FileName: SwallowGuestChat.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Stop Guests from chatting and give them a message telling them to sign up.
|
||||
-- // Guests are generally not allowed to chat, so please do not remove this.
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatSettings)
|
||||
local LocalPlayer = game:GetService("Players").LocalPlayer
|
||||
if LocalPlayer and LocalPlayer.UserId < 0 and not RunService:IsStudio() then
|
||||
|
||||
local channelObj = ChatWindow:GetCurrentChannel()
|
||||
if channelObj then
|
||||
util:SendSystemMessageToSelf(
|
||||
ChatLocalization:Get("GameChat_SwallowGuestChat_Message","Create a free account to get access to chat permissions!"),
|
||||
channelObj,
|
||||
{}
|
||||
)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
-- // FileName: ClearMessages.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Command to switch channel.
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatSettings)
|
||||
if string.sub(message, 1, 3):lower() ~= "/c " then
|
||||
return false
|
||||
end
|
||||
|
||||
local channelName = string.sub(message, 4)
|
||||
|
||||
local targetChannel = ChatWindow:GetChannel(channelName)
|
||||
if targetChannel then
|
||||
ChatWindow:SwitchCurrentChannel(channelName)
|
||||
if not ChatSettings.ShowChannelsBar then
|
||||
local currentChannel = ChatWindow:GetCurrentChannel()
|
||||
if currentChannel then
|
||||
util:SendSystemMessageToSelf(
|
||||
string.gsub(ChatLocalization:Get(
|
||||
"GameChat_SwitchChannel_NowInChannel",
|
||||
string.format("You are now chatting in channel: '%s'", channelName)
|
||||
),"{RBX_NAME}",channelName),
|
||||
targetChannel, {}
|
||||
)
|
||||
end
|
||||
end
|
||||
else
|
||||
local currentChannel = ChatWindow:GetCurrentChannel()
|
||||
if currentChannel then
|
||||
util:SendSystemMessageToSelf(
|
||||
string.gsub(ChatLocalization:Get(
|
||||
"GameChat_SwitchChannel_NotInChannel",
|
||||
string.format("You are not in channel: '%s'", channelName)
|
||||
),"{RBX_NAME}",channelName),
|
||||
currentChannel, {ChatColor = Color3.fromRGB(245, 50, 50)}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.COMPLETED_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
-- // FileName: Team.lua
|
||||
-- // Written by: Partixel/TheGamer101
|
||||
-- // Description: Team chat bar manipulation.
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
|
||||
local TEAM_COMMANDS = {"/team ", "/t ", "% "}
|
||||
|
||||
function IsTeamCommand(message)
|
||||
for i = 1, #TEAM_COMMANDS do
|
||||
local teamCommand = TEAM_COMMANDS[i]
|
||||
if string.sub(message, 1, teamCommand:len()):lower() == teamCommand then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local teamStateMethods = {}
|
||||
teamStateMethods.__index = teamStateMethods
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
local TeamCustomState = {}
|
||||
|
||||
function teamStateMethods:EnterTeamChat()
|
||||
self.TeamChatEntered = true
|
||||
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
|
||||
self.MessageModeButton.Text = "[Team]"
|
||||
self.MessageModeButton.TextColor3 = self:GetTeamChatColor()
|
||||
|
||||
local xSize = self.MessageModeButton.TextBounds.X
|
||||
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
|
||||
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
|
||||
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
|
||||
self.OriginalTeamText = self.TextBox.Text
|
||||
self.TextBox.Text = " "
|
||||
end
|
||||
|
||||
function teamStateMethods:TextUpdated()
|
||||
local newText = self.TextBox.Text
|
||||
if not self.TeamChatEntered then
|
||||
if IsTeamCommand(newText) then
|
||||
self:EnterTeamChat()
|
||||
end
|
||||
else
|
||||
if newText == "" then
|
||||
self.MessageModeButton.Text = ""
|
||||
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
|
||||
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
|
||||
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
|
||||
self.TextBox.Text = ""
|
||||
---Implement this when setting cursor positon is a thing.
|
||||
---self.TextBox.Text = self.OriginalTeamText
|
||||
self.TeamChatEntered = false
|
||||
---Temporary until setting cursor position...
|
||||
self.ChatBar:ResetCustomState()
|
||||
self.ChatBar:CaptureFocus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function teamStateMethods:GetMessage()
|
||||
if self.TeamChatEntered then
|
||||
return "/t " ..self.TextBox.Text
|
||||
end
|
||||
return self.TextBox.Text
|
||||
end
|
||||
|
||||
function teamStateMethods:ProcessCompletedMessage()
|
||||
return false
|
||||
end
|
||||
|
||||
function teamStateMethods:Destroy()
|
||||
self.MessageModeConnection:disconnect()
|
||||
self.Destroyed = true
|
||||
end
|
||||
|
||||
function teamStateMethods:GetTeamChatColor()
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
if LocalPlayer.Team then
|
||||
return LocalPlayer.Team.TeamColor.Color
|
||||
end
|
||||
if self.ChatSettings.DefaultChannelNameColor then
|
||||
return self.ChatSettings.DefaultChannelNameColor
|
||||
end
|
||||
return Color3.fromRGB(35, 76, 142)
|
||||
end
|
||||
|
||||
function TeamCustomState.new(ChatWindow, ChatBar, ChatSettings)
|
||||
local obj = setmetatable({}, teamStateMethods)
|
||||
obj.Destroyed = false
|
||||
obj.ChatWindow = ChatWindow
|
||||
obj.ChatBar = ChatBar
|
||||
obj.ChatSettings = ChatSettings
|
||||
obj.TextBox = ChatBar:GetTextBox()
|
||||
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
|
||||
obj.OriginalTeamText = ""
|
||||
obj.TeamChatEntered = false
|
||||
|
||||
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
|
||||
local chatBarText = obj.TextBox.Text
|
||||
if string.sub(chatBarText, 1, 1) == " " then
|
||||
chatBarText = string.sub(chatBarText, 2)
|
||||
end
|
||||
obj.ChatBar:ResetCustomState()
|
||||
obj.ChatBar:SetTextBoxText(chatBarText)
|
||||
obj.ChatBar:CaptureFocus()
|
||||
end)
|
||||
|
||||
obj:EnterTeamChat()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
|
||||
if ChatBar.TargetChannel == "Team" then
|
||||
return
|
||||
end
|
||||
|
||||
if IsTeamCommand(message) then
|
||||
return TeamCustomState.new(ChatWindow, ChatBar, ChatSettings)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
-- // FileName: Util.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Module for shared code between CommandModules.
|
||||
|
||||
--[[
|
||||
Creating a command module:
|
||||
1) Create a new module inside the CommandModules folder.
|
||||
2) Create a function that takes a message, the ChatWindow object and the ChatSettings and returns
|
||||
a bool command processed.
|
||||
3) Return this function from the module.
|
||||
--]]
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local COMMAND_MODULES_VERSION = 1
|
||||
|
||||
local KEY_COMMAND_PROCESSOR_TYPE = "ProcessorType"
|
||||
local KEY_PROCESSOR_FUNCTION = "ProcessorFunction"
|
||||
|
||||
---Command types.
|
||||
---Process a command as it is being typed. This allows for manipulation of the chat bar.
|
||||
local IN_PROGRESS_MESSAGE_PROCESSOR = 0
|
||||
---Simply process a completed message.
|
||||
local COMPLETED_MESSAGE_PROCESSOR = 1
|
||||
|
||||
local module = {}
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:SendSystemMessageToSelf(message, channelObj, extraData)
|
||||
local messageData =
|
||||
{
|
||||
ID = -1,
|
||||
FromSpeaker = nil,
|
||||
SpeakerUserId = 0,
|
||||
OriginalChannel = channelObj.Name,
|
||||
IsFiltered = true,
|
||||
MessageLength = string.len(message),
|
||||
Message = message,
|
||||
MessageType = ChatConstants.MessageTypeSystem,
|
||||
Time = os.time(),
|
||||
ExtraData = extraData,
|
||||
}
|
||||
|
||||
channelObj:AddMessageToChannel(messageData)
|
||||
end
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.COMMAND_MODULES_VERSION = COMMAND_MODULES_VERSION
|
||||
|
||||
obj.KEY_COMMAND_PROCESSOR_TYPE = KEY_COMMAND_PROCESSOR_TYPE
|
||||
obj.KEY_PROCESSOR_FUNCTION = KEY_PROCESSOR_FUNCTION
|
||||
|
||||
obj.IN_PROGRESS_MESSAGE_PROCESSOR = IN_PROGRESS_MESSAGE_PROCESSOR
|
||||
obj.COMPLETED_MESSAGE_PROCESSOR = COMPLETED_MESSAGE_PROCESSOR
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module.new()
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
-- // FileName: Whisper.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Whisper chat bar manipulation.
|
||||
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
local ChatSettings = require(script.Parent.Parent:WaitForChild("ChatSettings"))
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
while LocalPlayer == nil do
|
||||
PlayersService.ChildAdded:wait()
|
||||
LocalPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
local whisperStateMethods = {}
|
||||
whisperStateMethods.__index = whisperStateMethods
|
||||
|
||||
local WhisperCustomState = {}
|
||||
|
||||
function whisperStateMethods:TrimWhisperCommand(text)
|
||||
if string.sub(text, 1, 3):lower() == "/w " then
|
||||
return string.sub(text, 4)
|
||||
elseif string.sub(text, 1, 9):lower() == "/whisper " then
|
||||
return string.sub(text, 10)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function whisperStateMethods:TrimWhiteSpace(text)
|
||||
local newText = string.gsub(text, "%s+", "")
|
||||
local wasWhitespaceTrimmed = text[#text] == " "
|
||||
return newText, wasWhitespaceTrimmed
|
||||
end
|
||||
|
||||
function whisperStateMethods:ShouldAutoCompleteNames()
|
||||
if ChatSettings.WhisperCommandAutoCompletePlayerNames ~= nil then
|
||||
return ChatSettings.WhisperCommandAutoCompletePlayerNames
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function whisperStateMethods:GetWhisperingPlayer(enteredText)
|
||||
enteredText = enteredText:lower()
|
||||
local trimmedText = self:TrimWhisperCommand(enteredText)
|
||||
if trimmedText then
|
||||
local possiblePlayerName, whitespaceTrimmed = self:TrimWhiteSpace(trimmedText)
|
||||
local possibleMatches = {}
|
||||
local players = PlayersService:GetPlayers()
|
||||
for i = 1, #players do
|
||||
if players[i] ~= LocalPlayer then
|
||||
local lowerPlayerName = players[i].Name:lower()
|
||||
if string.sub(lowerPlayerName, 1, string.len(possiblePlayerName)) == possiblePlayerName then
|
||||
possibleMatches[players[i]] = players[i].Name:lower()
|
||||
end
|
||||
end
|
||||
end
|
||||
local matchCount = 0
|
||||
local lastMatch = nil
|
||||
local lastMatchName = nil
|
||||
for player, playerName in pairs(possibleMatches) do
|
||||
matchCount = matchCount + 1
|
||||
lastMatch = player
|
||||
lastMatchName = playerName
|
||||
if playerName == possiblePlayerName and whitespaceTrimmed then
|
||||
return player
|
||||
end
|
||||
end
|
||||
if matchCount == 1 then
|
||||
if self:ShouldAutoCompleteNames() then
|
||||
return lastMatch
|
||||
elseif lastMatchName == possiblePlayerName then
|
||||
return lastMatch
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function whisperStateMethods:GetWhisperChanneNameColor()
|
||||
if self.ChatSettings.WhisperChannelNameColor then
|
||||
return self.ChatSettings.WhisperChannelNameColor
|
||||
end
|
||||
return Color3.fromRGB(102, 14, 102)
|
||||
end
|
||||
|
||||
function whisperStateMethods:TextUpdated()
|
||||
local newText = self.TextBox.Text
|
||||
if not self.PlayerNameEntered then
|
||||
local player = self:GetWhisperingPlayer(newText)
|
||||
if player then
|
||||
self.PlayerNameEntered = true
|
||||
self.PlayerName = player.Name
|
||||
|
||||
self.MessageModeButton.Size = UDim2.new(0, 1000, 1, 0)
|
||||
self.MessageModeButton.Text = string.format("[To %s]", player.Name)
|
||||
self.MessageModeButton.TextColor3 = self:GetWhisperChanneNameColor()
|
||||
|
||||
local xSize = self.MessageModeButton.TextBounds.X
|
||||
self.MessageModeButton.Size = UDim2.new(0, xSize, 1, 0)
|
||||
self.TextBox.Size = UDim2.new(1, -xSize, 1, 0)
|
||||
self.TextBox.Position = UDim2.new(0, xSize, 0, 0)
|
||||
self.TextBox.Text = " "
|
||||
end
|
||||
else
|
||||
if newText == "" then
|
||||
self.MessageModeButton.Text = ""
|
||||
self.MessageModeButton.Size = UDim2.new(0, 0, 0, 0)
|
||||
self.TextBox.Size = UDim2.new(1, 0, 1, 0)
|
||||
self.TextBox.Position = UDim2.new(0, 0, 0, 0)
|
||||
self.TextBox.Text = ""
|
||||
---Implement this when setting cursor positon is a thing.
|
||||
---self.TextBox.Text = self.OriginalText .. " " .. self.PlayerName
|
||||
self.PlayerNameEntered = false
|
||||
---Temporary until setting cursor position...
|
||||
self.ChatBar:ResetCustomState()
|
||||
self.ChatBar:CaptureFocus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function whisperStateMethods:GetMessage()
|
||||
if self.PlayerNameEntered then
|
||||
return "/w " ..self.PlayerName.. " " ..self.TextBox.Text
|
||||
end
|
||||
return self.TextBox.Text
|
||||
end
|
||||
|
||||
function whisperStateMethods:ProcessCompletedMessage()
|
||||
return false
|
||||
end
|
||||
|
||||
function whisperStateMethods:Destroy()
|
||||
self.MessageModeConnection:disconnect()
|
||||
self.Destroyed = true
|
||||
end
|
||||
|
||||
function WhisperCustomState.new(ChatWindow, ChatBar, ChatSettings)
|
||||
local obj = setmetatable({}, whisperStateMethods)
|
||||
obj.Destroyed = false
|
||||
obj.ChatWindow = ChatWindow
|
||||
obj.ChatBar = ChatBar
|
||||
obj.ChatSettings = ChatSettings
|
||||
obj.TextBox = ChatBar:GetTextBox()
|
||||
obj.MessageModeButton = ChatBar:GetMessageModeTextButton()
|
||||
obj.OriginalWhisperText = ""
|
||||
obj.PlayerNameEntered = false
|
||||
|
||||
obj.MessageModeConnection = obj.MessageModeButton.MouseButton1Click:connect(function()
|
||||
local chatBarText = obj.TextBox.Text
|
||||
if string.sub(chatBarText, 1, 1) == " " then
|
||||
chatBarText = string.sub(chatBarText, 2)
|
||||
end
|
||||
obj.ChatBar:ResetCustomState()
|
||||
obj.ChatBar:SetTextBoxText(chatBarText)
|
||||
obj.ChatBar:CaptureFocus()
|
||||
end)
|
||||
|
||||
obj:TextUpdated()
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function ProcessMessage(message, ChatWindow, ChatBar, ChatSettings)
|
||||
if string.sub(message, 1, 3):lower() == "/w " or string.sub(message, 1, 9):lower() == "/whisper " then
|
||||
return WhisperCustomState.new(ChatWindow, ChatBar, ChatSettings)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_COMMAND_PROCESSOR_TYPE] = util.IN_PROGRESS_MESSAGE_PROCESSOR,
|
||||
[util.KEY_PROCESSOR_FUNCTION] = ProcessMessage
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
-- // FileName: DefaultChatMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a standard chat message.
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateMessageLabel(messageData, channelName)
|
||||
|
||||
local fromSpeaker = messageData.FromSpeaker
|
||||
local message = messageData.Message
|
||||
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or ChatSettings.DefaultFont
|
||||
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
|
||||
local useNameColor = extraData.NameColor or ChatSettings.DefaultNameColor
|
||||
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
|
||||
local useChannelColor = extraData.ChannelColor or useChatColor
|
||||
local tags = extraData.Tags or {}
|
||||
|
||||
local formatUseName = string.format("[%s]:", fromSpeaker)
|
||||
local speakerNameSize = util:GetStringTextBounds(formatUseName, useFont, useTextSize)
|
||||
local numNeededSpaces = util:GetNumberOfSpaces(formatUseName, useFont, useTextSize) + 1
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
|
||||
local NameButton = util:AddNameButtonToBaseMessage(BaseMessage, useNameColor, formatUseName, fromSpeaker)
|
||||
local ChannelButton = nil
|
||||
|
||||
local guiObjectSpacing = UDim2.new(0, 0, 0, 0)
|
||||
|
||||
if channelName ~= messageData.OriginalChannel then
|
||||
local formatChannelName = string.format("{%s}", messageData.OriginalChannel)
|
||||
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
|
||||
guiObjectSpacing = UDim2.new(0, ChannelButton.Size.X.Offset + util:GetStringTextBounds(" ", useFont, useTextSize).X, 0, 0)
|
||||
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize) + 1
|
||||
end
|
||||
|
||||
local tagLabels = {}
|
||||
for i, tag in pairs(tags) do
|
||||
local tagColor = tag.TagColor or Color3.fromRGB(255, 0, 255)
|
||||
local tagText = tag.TagText or "???"
|
||||
local formatTagText = string.format("[%s] ", tagText)
|
||||
local label = util:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
|
||||
label.Position = guiObjectSpacing
|
||||
|
||||
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatTagText, useFont, useTextSize)
|
||||
guiObjectSpacing = guiObjectSpacing + UDim2.new(0, label.Size.X.Offset, 0, 0)
|
||||
|
||||
table.insert(tagLabels, label)
|
||||
end
|
||||
|
||||
NameButton.Position = guiObjectSpacing
|
||||
|
||||
local function UpdateTextFunction(messageObject)
|
||||
if messageData.IsFiltered then
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. messageObject.Message
|
||||
else
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageObject.MessageLength)
|
||||
end
|
||||
end
|
||||
|
||||
UpdateTextFunction(messageData)
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[NameButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
for i, tagLabel in pairs(tagLabels) do
|
||||
local index = string.format("Tag%d", i)
|
||||
FadeParmaters[tagLabel] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
if ChannelButton then
|
||||
FadeParmaters[ChannelButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeDefault,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateMessageLabel
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
-- // FileName: MeCommandMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a me command message.
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateMeCommandMessageLabel(messageData, channelName)
|
||||
local message = messageData.Message
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or Enum.Font.SourceSansItalic
|
||||
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
|
||||
local useChatColor = Color3.new(1, 1, 1)
|
||||
local useChannelColor = extraData.ChannelColor or useChatColor
|
||||
local numNeededSpaces = 0
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
|
||||
local ChannelButton = nil
|
||||
|
||||
if channelName ~= messageData.OriginalChannel then
|
||||
local formatChannelName = string.format("{%s}", messageData.OriginalChannel)
|
||||
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
|
||||
numNeededSpaces = util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize) + 1
|
||||
end
|
||||
|
||||
local function UpdateTextFunction(messageObject)
|
||||
if messageData.IsFiltered then
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. messageObject.FromSpeaker .. " " .. string.sub(messageObject.Message, 5)
|
||||
else
|
||||
local messageLength = string.len(messageObject.FromSpeaker) + messageObject.MessageLength - 4
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageLength)
|
||||
end
|
||||
end
|
||||
|
||||
UpdateTextFunction(messageData)
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
if ChannelButton then
|
||||
FadeParmaters[ChannelButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeMeCommand,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateMeCommandMessageLabel
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
-- // FileName: SetCoreMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a message created with SetCore(ChatMakeSystemMessage).
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateSetCoreMessageLabel(messageData, channelName)
|
||||
local message = messageData.Message
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or ChatSettings.DefaultFont
|
||||
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
|
||||
local useColor = extraData.Color or ChatSettings.DefaultMessageColor
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useColor)
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = nil,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeSetCore,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateSetCoreMessageLabel
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
-- // FileName: SystemMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a system message.
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateSystemMessageLabel(messageData, channelName)
|
||||
local message = messageData.Message
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or ChatSettings.DefaultFont
|
||||
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
|
||||
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
|
||||
local useChannelColor = extraData.ChannelColor or useChatColor
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useChatColor)
|
||||
local ChannelButton = nil
|
||||
|
||||
if channelName ~= messageData.OriginalChannel then
|
||||
local formatChannelName = string.format("{%s}", messageData.OriginalChannel)
|
||||
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
|
||||
local numNeededSpaces = util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize) + 1
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. message
|
||||
end
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
if ChannelButton then
|
||||
FadeParmaters[ChannelButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = nil,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeSystem,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateSystemMessageLabel
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
-- // FileName: UnknownMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Default handler for message types with no other creator registered.
|
||||
-- // Just print that there was a message with no creator for now.
|
||||
|
||||
local MESSAGE_TYPE = "UnknownMessage"
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateUnknownMessageLabel(messageData)
|
||||
print("No message creator for message: " ..messageData.Message)
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = MESSAGE_TYPE,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateUnknownMessageLabel
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
-- // FileName: Util.lua
|
||||
-- // Written by: Xsitsu, TheGamer101
|
||||
-- // Description: Module for shared code between MessageCreatorModules.
|
||||
|
||||
--[[
|
||||
Creating a message creator module:
|
||||
1) Create a new module inside the MessageCreatorModules folder.
|
||||
2) Create a function that takes a messageData object and returns:
|
||||
{
|
||||
KEY_BASE_FRAME = BaseFrame,
|
||||
KEY_BASE_MESSAGE = BaseMessage,
|
||||
KEY_UPDATE_TEXT_FUNC = function(newMessageObject) ---Function to update the text of the message.
|
||||
KEY_GET_HEIGHT = function() ---Function to get the height of the message in absolute pixels,
|
||||
KEY_FADE_IN = function(duration, CurveUtil) ---Function to tell the message to start fading in.
|
||||
KEY_FADE_OUT = function(duration, CurveUtil) ---Function to tell the message to start fading out.
|
||||
KEY_UPDATE_ANIMATION = function(dtScale, CurveUtil) ---Update animation function.
|
||||
}
|
||||
3) return the following format from the module:
|
||||
{
|
||||
KEY_MESSAGE_TYPE = "Message type this module creates messages for."
|
||||
KEY_CREATOR_FUNCTION = YourFunctionHere
|
||||
}
|
||||
--]]
|
||||
|
||||
local DEFAULT_MESSAGE_CREATOR = "UnknownMessage"
|
||||
local MESSAGE_CREATOR_MODULES_VERSION = 1
|
||||
---Creator Module Object Keys
|
||||
local KEY_MESSAGE_TYPE = "MessageType"
|
||||
local KEY_CREATOR_FUNCTION = "MessageCreatorFunc"
|
||||
---Creator function return object keys
|
||||
local KEY_BASE_FRAME = "BaseFrame"
|
||||
local KEY_BASE_MESSAGE = "BaseMessage"
|
||||
local KEY_UPDATE_TEXT_FUNC = "UpdateTextFunction"
|
||||
local KEY_GET_HEIGHT = "GetHeightFunction"
|
||||
local KEY_FADE_IN = "FadeInFunction"
|
||||
local KEY_FADE_OUT = "FadeOutFunction"
|
||||
local KEY_UPDATE_ANIMATION = "UpdateAnimFunction"
|
||||
|
||||
local TextService = game:GetService("TextService")
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
while not LocalPlayer do
|
||||
Players.ChildAdded:wait()
|
||||
LocalPlayer = Players.LocalPlayer
|
||||
end
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local okShouldClipInGameChat, valueShouldClipInGameChat = pcall(function() return UserSettings():IsUserFeatureEnabled("UserShouldClipInGameChat") end)
|
||||
local shouldClipInGameChat = okShouldClipInGameChat and valueShouldClipInGameChat
|
||||
|
||||
local module = {}
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:GetStringTextBounds(text, font, textSize, sizeBounds)
|
||||
sizeBounds = sizeBounds or Vector2.new(10000, 10000)
|
||||
return TextService:GetTextSize(text, textSize, font, sizeBounds)
|
||||
end
|
||||
--// Above was taken directly from Util.GetStringTextBounds() in the old chat corescripts.
|
||||
|
||||
function methods:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
xSize = xSize or BaseFrame.AbsoluteSize.X
|
||||
local textBoundsSize = self:GetStringTextBounds(BaseMessage.Text, BaseMessage.Font, BaseMessage.TextSize, Vector2.new(xSize, 1000))
|
||||
return textBoundsSize.Y
|
||||
end
|
||||
|
||||
function methods:GetNumberOfSpaces(str, font, textSize)
|
||||
local strSize = self:GetStringTextBounds(str, font, textSize)
|
||||
local singleSpaceSize = self:GetStringTextBounds(" ", font, textSize)
|
||||
return math.ceil(strSize.X / singleSpaceSize.X)
|
||||
end
|
||||
|
||||
function methods:CreateBaseMessage(message, font, textSize, chatColor)
|
||||
local BaseFrame = self:GetFromObjectPool("Frame")
|
||||
BaseFrame.Selectable = false
|
||||
BaseFrame.Size = UDim2.new(1, 0, 0, 18)
|
||||
BaseFrame.Visible = true
|
||||
BaseFrame.BackgroundTransparency = 1
|
||||
|
||||
local messageBorder = 8
|
||||
|
||||
local BaseMessage = self:GetFromObjectPool("TextLabel")
|
||||
BaseMessage.Selectable = false
|
||||
BaseMessage.Size = UDim2.new(1, -(messageBorder + 6), 1, 0)
|
||||
BaseMessage.Position = UDim2.new(0, messageBorder, 0, 0)
|
||||
BaseMessage.BackgroundTransparency = 1
|
||||
BaseMessage.Font = font
|
||||
BaseMessage.TextSize = textSize
|
||||
BaseMessage.TextXAlignment = Enum.TextXAlignment.Left
|
||||
BaseMessage.TextYAlignment = Enum.TextYAlignment.Top
|
||||
BaseMessage.TextTransparency = 0
|
||||
BaseMessage.TextStrokeTransparency = 0.75
|
||||
BaseMessage.TextColor3 = chatColor
|
||||
BaseMessage.TextWrapped = true
|
||||
if shouldClipInGameChat then
|
||||
BaseMessage.ClipsDescendants = true
|
||||
end
|
||||
BaseMessage.Text = message
|
||||
BaseMessage.Visible = true
|
||||
BaseMessage.Parent = BaseFrame
|
||||
|
||||
return BaseFrame, BaseMessage
|
||||
end
|
||||
|
||||
function methods:AddNameButtonToBaseMessage(BaseMessage, nameColor, formatName, playerName)
|
||||
local speakerNameSize = self:GetStringTextBounds(formatName, BaseMessage.Font, BaseMessage.TextSize)
|
||||
local NameButton = self:GetFromObjectPool("TextButton")
|
||||
NameButton.Selectable = false
|
||||
NameButton.Size = UDim2.new(0, speakerNameSize.X, 0, speakerNameSize.Y)
|
||||
NameButton.Position = UDim2.new(0, 0, 0, 0)
|
||||
NameButton.BackgroundTransparency = 1
|
||||
NameButton.Font = BaseMessage.Font
|
||||
NameButton.TextSize = BaseMessage.TextSize
|
||||
NameButton.TextXAlignment = BaseMessage.TextXAlignment
|
||||
NameButton.TextYAlignment = BaseMessage.TextYAlignment
|
||||
NameButton.TextTransparency = BaseMessage.TextTransparency
|
||||
NameButton.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
|
||||
NameButton.TextColor3 = nameColor
|
||||
NameButton.Text = formatName
|
||||
NameButton.Visible = true
|
||||
NameButton.Parent = BaseMessage
|
||||
|
||||
local clickedConn = NameButton.MouseButton1Click:connect(function()
|
||||
self:NameButtonClicked(NameButton, playerName)
|
||||
end)
|
||||
|
||||
local changedConn = nil
|
||||
changedConn = NameButton.Changed:connect(function(prop)
|
||||
if prop == "Parent" then
|
||||
clickedConn:Disconnect()
|
||||
changedConn:Disconnect()
|
||||
end
|
||||
end)
|
||||
|
||||
return NameButton
|
||||
end
|
||||
|
||||
function methods:AddChannelButtonToBaseMessage(BaseMessage, channelColor, formatChannelName, channelName)
|
||||
local channelNameSize = self:GetStringTextBounds(formatChannelName, BaseMessage.Font, BaseMessage.TextSize)
|
||||
local ChannelButton = self:GetFromObjectPool("TextButton")
|
||||
ChannelButton.Selectable = false
|
||||
ChannelButton.Size = UDim2.new(0, channelNameSize.X, 0, channelNameSize.Y)
|
||||
ChannelButton.Position = UDim2.new(0, 0, 0, 0)
|
||||
ChannelButton.BackgroundTransparency = 1
|
||||
ChannelButton.Font = BaseMessage.Font
|
||||
ChannelButton.TextSize = BaseMessage.TextSize
|
||||
ChannelButton.TextXAlignment = BaseMessage.TextXAlignment
|
||||
ChannelButton.TextYAlignment = BaseMessage.TextYAlignment
|
||||
ChannelButton.TextTransparency = BaseMessage.TextTransparency
|
||||
ChannelButton.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
|
||||
ChannelButton.TextColor3 = channelColor
|
||||
ChannelButton.Text = formatChannelName
|
||||
ChannelButton.Visible = true
|
||||
ChannelButton.Parent = BaseMessage
|
||||
|
||||
local clickedConn = ChannelButton.MouseButton1Click:connect(function()
|
||||
self:ChannelButtonClicked(ChannelButton, channelName)
|
||||
end)
|
||||
|
||||
local changedConn = nil
|
||||
changedConn = ChannelButton.Changed:connect(function(prop)
|
||||
if prop == "Parent" then
|
||||
clickedConn:Disconnect()
|
||||
changedConn:Disconnect()
|
||||
end
|
||||
end)
|
||||
|
||||
return ChannelButton
|
||||
end
|
||||
|
||||
function methods:AddTagLabelToBaseMessage(BaseMessage, tagColor, formatTagText)
|
||||
local tagNameSize = self:GetStringTextBounds(formatTagText, BaseMessage.Font, BaseMessage.TextSize)
|
||||
local TagLabel = self:GetFromObjectPool("TextLabel")
|
||||
TagLabel.Selectable = false
|
||||
TagLabel.Size = UDim2.new(0, tagNameSize.X, 0, tagNameSize.Y)
|
||||
TagLabel.Position = UDim2.new(0, 0, 0, 0)
|
||||
TagLabel.BackgroundTransparency = 1
|
||||
TagLabel.Font = BaseMessage.Font
|
||||
TagLabel.TextSize = BaseMessage.TextSize
|
||||
TagLabel.TextXAlignment = BaseMessage.TextXAlignment
|
||||
TagLabel.TextYAlignment = BaseMessage.TextYAlignment
|
||||
TagLabel.TextTransparency = BaseMessage.TextTransparency
|
||||
TagLabel.TextStrokeTransparency = BaseMessage.TextStrokeTransparency
|
||||
TagLabel.TextColor3 = tagColor
|
||||
TagLabel.Text = formatTagText
|
||||
TagLabel.Visible = true
|
||||
TagLabel.Parent = BaseMessage
|
||||
|
||||
return TagLabel
|
||||
end
|
||||
|
||||
function GetWhisperChannelPrefix()
|
||||
if ChatConstants.WhisperChannelPrefix then
|
||||
return ChatConstants.WhisperChannelPrefix
|
||||
end
|
||||
return "To "
|
||||
end
|
||||
|
||||
function methods:NameButtonClicked(nameButton, playerName)
|
||||
if not self.ChatWindow then
|
||||
return
|
||||
end
|
||||
|
||||
if ChatSettings.ClickOnPlayerNameToWhisper then
|
||||
local player = Players:FindFirstChild(playerName)
|
||||
if player and player ~= LocalPlayer then
|
||||
local whisperChannel = GetWhisperChannelPrefix() ..playerName
|
||||
if self.ChatWindow:GetChannel(whisperChannel) then
|
||||
self.ChatBar:ResetCustomState()
|
||||
local targetChannelName = self.ChatWindow:GetTargetMessageChannel()
|
||||
if targetChannelName ~= whisperChannel then
|
||||
self.ChatWindow:SwitchCurrentChannel(whisperChannel)
|
||||
end
|
||||
|
||||
local whisperMessage = "/w " ..playerName
|
||||
self.ChatBar:SetText(whisperMessage)
|
||||
|
||||
self.ChatBar:CaptureFocus()
|
||||
elseif not self.ChatBar:IsInCustomState() then
|
||||
local whisperMessage = "/w " ..playerName
|
||||
self.ChatBar:SetText(whisperMessage)
|
||||
|
||||
self.ChatBar:CaptureFocus()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ChannelButtonClicked(channelButton, channelName)
|
||||
if not self.ChatWindow then
|
||||
return
|
||||
end
|
||||
|
||||
if ChatSettings.ClickOnChannelNameToSetMainChannel then
|
||||
if self.ChatWindow:GetChannel(channelName) then
|
||||
self.ChatBar:ResetCustomState()
|
||||
local targetChannelName = self.ChatWindow:GetTargetMessageChannel()
|
||||
if targetChannelName ~= channelName then
|
||||
self.ChatWindow:SwitchCurrentChannel(channelName)
|
||||
end
|
||||
self.ChatBar:ResetText()
|
||||
self.ChatBar:CaptureFocus()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RegisterChatWindow(chatWindow)
|
||||
self.ChatWindow = chatWindow
|
||||
self.ChatBar = chatWindow:GetChatBar()
|
||||
end
|
||||
|
||||
function methods:GetFromObjectPool(className)
|
||||
if self.ObjectPool == nil then
|
||||
return Instance.new(className)
|
||||
end
|
||||
return self.ObjectPool:GetInstance(className)
|
||||
end
|
||||
|
||||
function methods:RegisterObjectPool(objectPool)
|
||||
self.ObjectPool = objectPool
|
||||
end
|
||||
|
||||
-- CreateFadeFunctions usage:
|
||||
-- fadeObjects is a map of text labels and button to start and end values for a given property.
|
||||
-- e.g {
|
||||
-- NameButton = {
|
||||
-- TextTransparency = {
|
||||
-- FadedIn = 0.5,
|
||||
-- FadedOut = 1,
|
||||
-- }
|
||||
-- },
|
||||
-- ImageOne = {
|
||||
-- ImageTransparency = {
|
||||
-- FadedIn = 0,
|
||||
-- FadedOut = 0.5,
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
function methods:CreateFadeFunctions(fadeObjects)
|
||||
local AnimParams = {}
|
||||
for object, properties in pairs(fadeObjects) do
|
||||
AnimParams[object] = {}
|
||||
for property, values in pairs(properties) do
|
||||
AnimParams[object][property] = {
|
||||
Target = values.FadedIn,
|
||||
Current = object[property],
|
||||
NormalizedExptValue = 1,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function FadeInFunction(duration, CurveUtil)
|
||||
for object, properties in pairs(AnimParams) do
|
||||
for property, values in pairs(properties) do
|
||||
values.Target = fadeObjects[object][property].FadedIn
|
||||
values.NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function FadeOutFunction(duration, CurveUtil)
|
||||
for object, properties in pairs(AnimParams) do
|
||||
for property, values in pairs(properties) do
|
||||
values.Target = fadeObjects[object][property].FadedOut
|
||||
values.NormalizedExptValue = CurveUtil:NormalizedDefaultExptValueInSeconds(duration)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function AnimGuiObjects()
|
||||
for object, properties in pairs(AnimParams) do
|
||||
for property, values in pairs(properties) do
|
||||
object[property] = values.Current
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateAnimFunction(dtScale, CurveUtil)
|
||||
for object, properties in pairs(AnimParams) do
|
||||
for property, values in pairs(properties) do
|
||||
values.Current = CurveUtil:Expt(
|
||||
values.Current,
|
||||
values.Target,
|
||||
values.NormalizedExptValue,
|
||||
dtScale
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
AnimGuiObjects()
|
||||
end
|
||||
|
||||
return FadeInFunction, FadeOutFunction, UpdateAnimFunction
|
||||
end
|
||||
|
||||
function methods:NewBindableEvent(name)
|
||||
local bindable = Instance.new("BindableEvent")
|
||||
bindable.Name = name
|
||||
return bindable
|
||||
end
|
||||
|
||||
--- DEPRECATED METHODS:
|
||||
function methods:RegisterGuiRoot()
|
||||
-- This is left here for compatibility with ChatScript versions lower than 0.5
|
||||
end
|
||||
--- End of Deprecated methods.
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.ObjectPool = nil
|
||||
obj.ChatWindow = nil
|
||||
|
||||
obj.DEFAULT_MESSAGE_CREATOR = DEFAULT_MESSAGE_CREATOR
|
||||
obj.MESSAGE_CREATOR_MODULES_VERSION = MESSAGE_CREATOR_MODULES_VERSION
|
||||
|
||||
obj.KEY_MESSAGE_TYPE = KEY_MESSAGE_TYPE
|
||||
obj.KEY_CREATOR_FUNCTION = KEY_CREATOR_FUNCTION
|
||||
|
||||
obj.KEY_BASE_FRAME = KEY_BASE_FRAME
|
||||
obj.KEY_BASE_MESSAGE = KEY_BASE_MESSAGE
|
||||
obj.KEY_UPDATE_TEXT_FUNC = KEY_UPDATE_TEXT_FUNC
|
||||
obj.KEY_GET_HEIGHT = KEY_GET_HEIGHT
|
||||
obj.KEY_FADE_IN = KEY_FADE_IN
|
||||
obj.KEY_FADE_OUT = KEY_FADE_OUT
|
||||
obj.KEY_UPDATE_ANIMATION = KEY_UPDATE_ANIMATION
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module.new()
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
-- // FileName: WelcomeMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a welcome message.
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateWelcomeMessageLabel(messageData, channelName)
|
||||
local message = messageData.Message
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or ChatSettings.DefaultFont
|
||||
local useTextSize = extraData.FontSize or ChatSettings.ChatWindowTextSize
|
||||
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
|
||||
local useChannelColor = extraData.ChannelColor or useChatColor
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage(message, useFont, useTextSize, useChatColor)
|
||||
local ChannelButton = nil
|
||||
|
||||
if channelName ~= messageData.OriginalChannel then
|
||||
local formatChannelName = string.format("{%s}", messageData.OriginalChannel)
|
||||
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
|
||||
local numNeededSpaces = util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize) + 1
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. message
|
||||
end
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
if ChannelButton then
|
||||
FadeParmaters[ChannelButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = nil,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeWelcome,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateWelcomeMessageLabel
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
-- // FileName: WhisperMessage.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Create a message label for a whisper chat message.
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
while not LocalPlayer do
|
||||
PlayersService.ChildAdded:wait()
|
||||
LocalPlayer = PlayersService.LocalPlayer
|
||||
end
|
||||
|
||||
local clientChatModules = script.Parent.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(clientChatModules:WaitForChild("ChatConstants"))
|
||||
local util = require(script.Parent:WaitForChild("Util"))
|
||||
|
||||
function CreateMessageLabel(messageData, channelName)
|
||||
|
||||
local fromSpeaker = messageData.FromSpeaker
|
||||
local message = messageData.Message
|
||||
|
||||
local extraData = messageData.ExtraData or {}
|
||||
local useFont = extraData.Font or ChatSettings.DefaultFont
|
||||
local useTextSize = extraData.TextSize or ChatSettings.ChatWindowTextSize
|
||||
local useNameColor = extraData.NameColor or ChatSettings.DefaultNameColor
|
||||
local useChatColor = extraData.ChatColor or ChatSettings.DefaultMessageColor
|
||||
local useChannelColor = extraData.ChannelColor or useChatColor
|
||||
|
||||
local formatUseName = string.format("[%s]:", fromSpeaker)
|
||||
local speakerNameSize = util:GetStringTextBounds(formatUseName, useFont, useTextSize)
|
||||
local numNeededSpaces = util:GetNumberOfSpaces(formatUseName, useFont, useTextSize) + 1
|
||||
|
||||
local BaseFrame, BaseMessage = util:CreateBaseMessage("", useFont, useTextSize, useChatColor)
|
||||
local NameButton = util:AddNameButtonToBaseMessage(BaseMessage, useNameColor, formatUseName, fromSpeaker)
|
||||
local ChannelButton = nil
|
||||
|
||||
if channelName ~= messageData.OriginalChannel then
|
||||
local whisperString = messageData.OriginalChannel
|
||||
if messageData.FromSpeaker ~= LocalPlayer.Name then
|
||||
whisperString = string.format("From %s", messageData.FromSpeaker)
|
||||
end
|
||||
|
||||
local formatChannelName = string.format("{%s}", whisperString)
|
||||
ChannelButton = util:AddChannelButtonToBaseMessage(BaseMessage, useChannelColor, formatChannelName, messageData.OriginalChannel)
|
||||
NameButton.Position = UDim2.new(0, ChannelButton.Size.X.Offset + util:GetStringTextBounds(" ", useFont, useTextSize).X, 0, 0)
|
||||
numNeededSpaces = numNeededSpaces + util:GetNumberOfSpaces(formatChannelName, useFont, useTextSize) + 1
|
||||
end
|
||||
|
||||
local function UpdateTextFunction(messageObject)
|
||||
if messageData.IsFiltered then
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. messageObject.Message
|
||||
else
|
||||
BaseMessage.Text = string.rep(" ", numNeededSpaces) .. string.rep("_", messageObject.MessageLength)
|
||||
end
|
||||
end
|
||||
|
||||
UpdateTextFunction(messageData)
|
||||
|
||||
local function GetHeightFunction(xSize)
|
||||
return util:GetMessageHeight(BaseMessage, BaseFrame, xSize)
|
||||
end
|
||||
|
||||
local FadeParmaters = {}
|
||||
FadeParmaters[NameButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
FadeParmaters[BaseMessage] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
|
||||
if ChannelButton then
|
||||
FadeParmaters[ChannelButton] = {
|
||||
TextTransparency = {FadedIn = 0, FadedOut = 1},
|
||||
TextStrokeTransparency = {FadedIn = 0.75, FadedOut = 1}
|
||||
}
|
||||
end
|
||||
|
||||
local FadeInFunction, FadeOutFunction, UpdateAnimFunction = util:CreateFadeFunctions(FadeParmaters)
|
||||
|
||||
return {
|
||||
[util.KEY_BASE_FRAME] = BaseFrame,
|
||||
[util.KEY_BASE_MESSAGE] = BaseMessage,
|
||||
[util.KEY_UPDATE_TEXT_FUNC] = UpdateTextFunction,
|
||||
[util.KEY_GET_HEIGHT] = GetHeightFunction,
|
||||
[util.KEY_FADE_IN] = FadeInFunction,
|
||||
[util.KEY_FADE_OUT] = FadeOutFunction,
|
||||
[util.KEY_UPDATE_ANIMATION] = UpdateAnimFunction
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
[util.KEY_MESSAGE_TYPE] = ChatConstants.MessageTypeWhisper,
|
||||
[util.KEY_CREATOR_FUNCTION] = CreateMessageLabel
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
-- // FileName: MessageLabelCreator.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module to handle taking text and creating stylized GUI objects for display in ChatWindow.
|
||||
|
||||
local OBJECT_POOL_SIZE = 50
|
||||
|
||||
local module = {}
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local messageCreatorModules = clientChatModules:WaitForChild("MessageCreatorModules")
|
||||
local messageCreatorUtil = require(messageCreatorModules:WaitForChild("Util"))
|
||||
local modulesFolder = script.Parent
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
local moduleObjectPool = require(modulesFolder:WaitForChild("ObjectPool"))
|
||||
local MessageSender = require(modulesFolder:WaitForChild("MessageSender"))
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
-- merge properties on both table to target
|
||||
function mergeProps(source, target)
|
||||
if not source then return end
|
||||
for prop, value in pairs(source) do
|
||||
target[prop] = value
|
||||
end
|
||||
end
|
||||
|
||||
function ReturnToObjectPoolRecursive(instance, objectPool)
|
||||
local children = instance:GetChildren()
|
||||
for i = 1, #children do
|
||||
ReturnToObjectPoolRecursive(children[i], objectPool)
|
||||
end
|
||||
instance.Parent = nil
|
||||
objectPool:ReturnInstance(instance)
|
||||
end
|
||||
|
||||
function GetMessageCreators()
|
||||
local typeToFunction = {}
|
||||
local creators = messageCreatorModules:GetChildren()
|
||||
for i = 1, #creators do
|
||||
if creators[i]:IsA("ModuleScript") then
|
||||
if creators[i].Name ~= "Util" then
|
||||
local creator = require(creators[i])
|
||||
typeToFunction[creator[messageCreatorUtil.KEY_MESSAGE_TYPE]] = creator[messageCreatorUtil.KEY_CREATOR_FUNCTION]
|
||||
end
|
||||
end
|
||||
end
|
||||
return typeToFunction
|
||||
end
|
||||
|
||||
function methods:WrapIntoMessageObject(messageData, createdMessageObject)
|
||||
local BaseFrame = createdMessageObject[messageCreatorUtil.KEY_BASE_FRAME]
|
||||
local BaseMessage = nil
|
||||
if messageCreatorUtil.KEY_BASE_MESSAGE then
|
||||
BaseMessage = createdMessageObject[messageCreatorUtil.KEY_BASE_MESSAGE]
|
||||
end
|
||||
local UpdateTextFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_TEXT_FUNC]
|
||||
local GetHeightFunction = createdMessageObject[messageCreatorUtil.KEY_GET_HEIGHT]
|
||||
local FadeInFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_IN]
|
||||
local FadeOutFunction = createdMessageObject[messageCreatorUtil.KEY_FADE_OUT]
|
||||
local UpdateAnimFunction = createdMessageObject[messageCreatorUtil.KEY_UPDATE_ANIMATION]
|
||||
|
||||
local obj = {}
|
||||
|
||||
obj.ID = messageData.ID
|
||||
obj.BaseFrame = BaseFrame
|
||||
obj.BaseMessage = BaseMessage
|
||||
obj.UpdateTextFunction = UpdateTextFunction or function() warn("NO MESSAGE RESIZE FUNCTION") end
|
||||
obj.GetHeightFunction = GetHeightFunction
|
||||
obj.FadeInFunction = FadeInFunction
|
||||
obj.FadeOutFunction = FadeOutFunction
|
||||
obj.UpdateAnimFunction = UpdateAnimFunction
|
||||
obj.ObjectPool = self.ObjectPool
|
||||
obj.Destroyed = false
|
||||
|
||||
function obj:Destroy()
|
||||
ReturnToObjectPoolRecursive(self.BaseFrame, self.ObjectPool)
|
||||
self.Destroyed = true
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function methods:CreateMessageLabel(messageData, currentChannelName)
|
||||
|
||||
messageData.Channel = currentChannelName
|
||||
local extraDeveloperFormatTable
|
||||
pcall(function()
|
||||
extraDeveloperFormatTable = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnClientFormattingMessage, messageData)
|
||||
end)
|
||||
messageData.ExtraData = messageData.ExtraData or {}
|
||||
mergeProps(extraDeveloperFormatTable, messageData.ExtraData)
|
||||
|
||||
local messageType = messageData.MessageType
|
||||
if self.MessageCreators[messageType] then
|
||||
local createdMessageObject = self.MessageCreators[messageType](messageData, currentChannelName)
|
||||
if createdMessageObject then
|
||||
return self:WrapIntoMessageObject(messageData, createdMessageObject)
|
||||
end
|
||||
elseif self.DefaultCreatorType then
|
||||
local createdMessageObject = self.MessageCreators[self.DefaultCreatorType](messageData, currentChannelName)
|
||||
if createdMessageObject then
|
||||
return self:WrapIntoMessageObject(messageData, createdMessageObject)
|
||||
end
|
||||
else
|
||||
error("No message creator available for message type: " ..messageType)
|
||||
end
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.ObjectPool = moduleObjectPool.new(OBJECT_POOL_SIZE)
|
||||
obj.MessageCreators = GetMessageCreators()
|
||||
obj.DefaultCreatorType = messageCreatorUtil.DEFAULT_MESSAGE_CREATOR
|
||||
|
||||
messageCreatorUtil:RegisterObjectPool(obj.ObjectPool)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function module:GetStringTextBounds(text, font, textSize, sizeBounds)
|
||||
return messageCreatorUtil:GetStringTextBounds(text, font, textSize, sizeBounds)
|
||||
end
|
||||
|
||||
return module
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
-- // FileName: MessageLogDisplay.lua
|
||||
-- // Written by: Xsitsu, TheGamer101
|
||||
-- // Description: ChatChannel window for displaying messages.
|
||||
|
||||
local module = {}
|
||||
module.ScrollBarThickness = 4
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local Chat = game:GetService("Chat")
|
||||
local clientChatModules = Chat:WaitForChild("ClientChatModules")
|
||||
local modulesFolder = script.Parent
|
||||
local moduleMessageLabelCreator = require(modulesFolder:WaitForChild("MessageLabelCreator"))
|
||||
local CurveUtil = require(modulesFolder:WaitForChild("CurveUtil"))
|
||||
|
||||
local ChatSettings = require(clientChatModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local FlagFixChatMessageLogPerformance = false do
|
||||
local ok, value = pcall(function()
|
||||
return settings():IsUserFeatureEnabled("UserFixChatMessageLogPerformance")
|
||||
end)
|
||||
if ok then
|
||||
FlagFixChatMessageLogPerformance = value
|
||||
end
|
||||
end
|
||||
|
||||
local MessageLabelCreator = moduleMessageLabelCreator.new()
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
local function CreateGuiObjects()
|
||||
local BaseFrame = Instance.new("Frame")
|
||||
BaseFrame.Selectable = false
|
||||
BaseFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
BaseFrame.BackgroundTransparency = 1
|
||||
|
||||
local Scroller = Instance.new("ScrollingFrame")
|
||||
Scroller.Selectable = ChatSettings.GamepadNavigationEnabled
|
||||
Scroller.Name = "Scroller"
|
||||
Scroller.BackgroundTransparency = 1
|
||||
Scroller.BorderSizePixel = 0
|
||||
Scroller.Position = UDim2.new(0, 0, 0, 3)
|
||||
Scroller.Size = UDim2.new(1, -4, 1, -6)
|
||||
Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
|
||||
Scroller.ScrollBarThickness = module.ScrollBarThickness
|
||||
Scroller.Active = false
|
||||
Scroller.Parent = BaseFrame
|
||||
|
||||
local Layout
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
Layout = Instance.new("UIListLayout")
|
||||
Layout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
Layout.Parent = Scroller
|
||||
end
|
||||
|
||||
return BaseFrame, Scroller, Layout
|
||||
end
|
||||
|
||||
function methods:Destroy()
|
||||
self.GuiObject:Destroy()
|
||||
self.Destroyed = true
|
||||
end
|
||||
|
||||
function methods:SetActive(active)
|
||||
self.GuiObject.Visible = active
|
||||
end
|
||||
|
||||
function methods:UpdateMessageFiltered(messageData)
|
||||
local messageObject = nil
|
||||
local searchIndex = 1
|
||||
local searchTable = self.MessageObjectLog
|
||||
|
||||
while (#searchTable >= searchIndex) do
|
||||
local obj = searchTable[searchIndex]
|
||||
|
||||
if obj.ID == messageData.ID then
|
||||
messageObject = obj
|
||||
break
|
||||
end
|
||||
|
||||
searchIndex = searchIndex + 1
|
||||
end
|
||||
|
||||
if messageObject then
|
||||
local isScrolledDown = self:IsScrolledDown()
|
||||
messageObject.UpdateTextFunction(messageData)
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
self:PositionMessageLabelInWindow(messageObject, isScrolledDown)
|
||||
else
|
||||
self:ReorderAllMessages()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:AddMessage(messageData)
|
||||
self:WaitUntilParentedCorrectly()
|
||||
|
||||
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
|
||||
if messageObject == nil then
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(self.MessageObjectLog, messageObject)
|
||||
self:PositionMessageLabelInWindow(messageObject)
|
||||
end
|
||||
|
||||
function methods:AddMessageAtIndex(messageData, index)
|
||||
local messageObject = MessageLabelCreator:CreateMessageLabel(messageData, self.CurrentChannelName)
|
||||
if messageObject == nil then
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(self.MessageObjectLog, index, messageObject)
|
||||
|
||||
local wasScrolledToBottom = self:IsScrolledDown()
|
||||
self:ReorderAllMessages()
|
||||
if wasScrolledToBottom then
|
||||
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RemoveLastMessage()
|
||||
self:WaitUntilParentedCorrectly()
|
||||
|
||||
local lastMessage = self.MessageObjectLog[1]
|
||||
-- remove with FlagFixChatMessageLogPerformance
|
||||
local posOffset = UDim2.new(0, 0, 0, lastMessage.BaseFrame.AbsoluteSize.Y)
|
||||
|
||||
lastMessage:Destroy()
|
||||
table.remove(self.MessageObjectLog, 1)
|
||||
|
||||
if not FlagFixChatMessageLogPerformance then
|
||||
for i, messageObject in pairs(self.MessageObjectLog) do
|
||||
messageObject.BaseFrame.Position = messageObject.BaseFrame.Position - posOffset
|
||||
end
|
||||
|
||||
self.Scroller.CanvasSize = self.Scroller.CanvasSize - posOffset
|
||||
end
|
||||
end
|
||||
|
||||
function methods:IsScrolledDown()
|
||||
local yCanvasSize = self.Scroller.CanvasSize.Y.Offset
|
||||
local yContainerSize = self.Scroller.AbsoluteWindowSize.Y
|
||||
local yScrolledPosition = self.Scroller.CanvasPosition.Y
|
||||
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
return
|
||||
yCanvasSize < yContainerSize or
|
||||
yCanvasSize + yScrolledPosition >= yContainerSize - 5
|
||||
else
|
||||
return (yCanvasSize < yContainerSize or
|
||||
yCanvasSize - yScrolledPosition <= yContainerSize + 5)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:PositionMessageLabelInWindow(messageObject, isScrolledDown)
|
||||
self:WaitUntilParentedCorrectly()
|
||||
|
||||
local baseFrame = messageObject.BaseFrame
|
||||
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
if isScrolledDown == nil then
|
||||
isScrolledDown = self:IsScrolledDown()
|
||||
end
|
||||
baseFrame.LayoutOrder = messageObject.ID
|
||||
else
|
||||
baseFrame.Parent = self.Scroller
|
||||
baseFrame.Position = UDim2.new(0, 0, 0, self.Scroller.CanvasSize.Y.Offset)
|
||||
end
|
||||
|
||||
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(self.Scroller.AbsoluteSize.X))
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
baseFrame.Parent = self.Scroller
|
||||
end
|
||||
|
||||
if messageObject.BaseMessage then
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
for i = 1, 10 do
|
||||
if messageObject.BaseMessage.TextFits then
|
||||
break
|
||||
end
|
||||
|
||||
local trySize = self.Scroller.AbsoluteSize.X - i
|
||||
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
|
||||
end
|
||||
else
|
||||
local trySize = self.Scroller.AbsoluteSize.X
|
||||
local minTrySize = math.min(self.Scroller.AbsoluteSize.X - 10, 0)
|
||||
while not messageObject.BaseMessage.TextFits do
|
||||
trySize = trySize - 1
|
||||
if trySize < minTrySize then
|
||||
break
|
||||
end
|
||||
baseFrame.Size = UDim2.new(1, 0, 0, messageObject.GetHeightFunction(trySize))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
if isScrolledDown then
|
||||
local scrollValue = self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y
|
||||
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, scrollValue))
|
||||
end
|
||||
else
|
||||
isScrolledDown = self:IsScrolledDown()
|
||||
|
||||
local add = UDim2.new(0, 0, 0, baseFrame.Size.Y.Offset)
|
||||
self.Scroller.CanvasSize = self.Scroller.CanvasSize + add
|
||||
|
||||
if isScrolledDown then
|
||||
self.Scroller.CanvasPosition = Vector2.new(0, math.max(0, self.Scroller.CanvasSize.Y.Offset - self.Scroller.AbsoluteSize.Y))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:ReorderAllMessages()
|
||||
self:WaitUntilParentedCorrectly()
|
||||
|
||||
--// Reordering / reparenting with a size less than 1 causes weird glitches to happen with scrolling as repositioning happens.
|
||||
if self.GuiObject.AbsoluteSize.Y < 1 then return end
|
||||
|
||||
local oldCanvasPositon = self.Scroller.CanvasPosition
|
||||
local wasScrolledDown = self:IsScrolledDown()
|
||||
|
||||
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
|
||||
for i, messageObject in pairs(self.MessageObjectLog) do
|
||||
self:PositionMessageLabelInWindow(messageObject)
|
||||
end
|
||||
|
||||
if not wasScrolledDown then
|
||||
self.Scroller.CanvasPosition = oldCanvasPositon
|
||||
end
|
||||
end
|
||||
|
||||
function methods:Clear()
|
||||
for i, v in pairs(self.MessageObjectLog) do
|
||||
v:Destroy()
|
||||
end
|
||||
self.MessageObjectLog = {}
|
||||
|
||||
if not FlagFixChatMessageLogPerformance then
|
||||
self.Scroller.CanvasSize = UDim2.new(0, 0, 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SetCurrentChannelName(name)
|
||||
self.CurrentChannelName = name
|
||||
end
|
||||
|
||||
function methods:FadeOutBackground(duration)
|
||||
--// Do nothing
|
||||
end
|
||||
|
||||
function methods:FadeInBackground(duration)
|
||||
--// Do nothing
|
||||
end
|
||||
|
||||
function methods:FadeOutText(duration)
|
||||
for i = 1, #self.MessageObjectLog do
|
||||
if self.MessageObjectLog[i].FadeOutFunction then
|
||||
self.MessageObjectLog[i].FadeOutFunction(duration, CurveUtil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:FadeInText(duration)
|
||||
for i = 1, #self.MessageObjectLog do
|
||||
if self.MessageObjectLog[i].FadeInFunction then
|
||||
self.MessageObjectLog[i].FadeInFunction(duration, CurveUtil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:Update(dtScale)
|
||||
for i = 1, #self.MessageObjectLog do
|
||||
if self.MessageObjectLog[i].UpdateAnimFunction then
|
||||
self.MessageObjectLog[i].UpdateAnimFunction(dtScale, CurveUtil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--// ToDo: Move to common modules
|
||||
function methods:WaitUntilParentedCorrectly()
|
||||
while (not self.GuiObject:IsDescendantOf(game:GetService("Players").LocalPlayer)) do
|
||||
self.GuiObject.AncestryChanged:wait()
|
||||
end
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
obj.Destroyed = false
|
||||
|
||||
local BaseFrame, Scroller, Layout = CreateGuiObjects()
|
||||
obj.GuiObject = BaseFrame
|
||||
obj.Scroller = Scroller
|
||||
obj.Layout = Layout
|
||||
|
||||
obj.MessageObjectLog = {}
|
||||
|
||||
obj.Name = "MessageLogDisplay"
|
||||
obj.GuiObject.Name = "Frame_" .. obj.Name
|
||||
|
||||
obj.CurrentChannelName = ""
|
||||
|
||||
obj.GuiObject:GetPropertyChangedSignal("AbsoluteSize"):Connect(function()
|
||||
spawn(function() obj:ReorderAllMessages() end)
|
||||
end)
|
||||
|
||||
if FlagFixChatMessageLogPerformance then
|
||||
local wasScrolledDown = true
|
||||
|
||||
obj.Layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
|
||||
local size = obj.Layout.AbsoluteContentSize
|
||||
obj.Scroller.CanvasSize = UDim2.new(0, 0, 0, size.Y)
|
||||
if wasScrolledDown then
|
||||
local windowSize = obj.Scroller.AbsoluteWindowSize
|
||||
obj.Scroller.CanvasPosition = Vector2.new(0, size.Y - windowSize.Y)
|
||||
end
|
||||
end)
|
||||
|
||||
obj.Scroller:GetPropertyChangedSignal("CanvasPosition"):Connect(function()
|
||||
wasScrolledDown = obj:IsScrolledDown()
|
||||
end)
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
-- // FileName: MessageSender.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module to centralize sending message functionality.
|
||||
|
||||
local module = {}
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local modulesFolder = script.Parent
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:SendMessage(message, toChannel)
|
||||
self.SayMessageRequest:FireServer(message, toChannel)
|
||||
end
|
||||
|
||||
function methods:RegisterSayMessageFunction(func)
|
||||
self.SayMessageRequest = func
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
obj.SayMessageRequest = nil
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module.new()
|
||||
@@ -0,0 +1,80 @@
|
||||
## Documentation
|
||||
### ChatWindow
|
||||
This is the main client side chat object.
|
||||
|
||||
### Methods
|
||||
AddChannel(string channelName)
|
||||
RemoveChannel(string channelName)
|
||||
ChatChannel GetChannel(string channelName)
|
||||
ChatChannel GetCurrentChannel()
|
||||
SwitchCurrentChannel(string channelName)
|
||||
|
||||
bool GetVisible()
|
||||
SetVisible(bool visible)
|
||||
|
||||
EnableResizable()
|
||||
DisableResizable()
|
||||
|
||||
FadeOutBackground(float duration)
|
||||
FadeInBackground(float duration)
|
||||
FadeOutText(float duration)
|
||||
FadeInText(float duration)
|
||||
|
||||
### Properties
|
||||
ChatBar ChatBar
|
||||
ChannelsBar ChannelsBar
|
||||
MessageLogDisplay MessageLogDisplay
|
||||
|
||||
### ChatChannel
|
||||
A client side chat channel object to manage the chat.
|
||||
|
||||
### Methods
|
||||
AddMessageToChannel(table messageData)
|
||||
RemoveLastMessageFromChannel()
|
||||
ClearMessageLog()
|
||||
|
||||
### MessageLogDisplay
|
||||
This object handles displaying the messages of the current channel.
|
||||
|
||||
### Methods
|
||||
AddMessage(table messageData)
|
||||
RemoveLastMessage()
|
||||
ReorderAllMessages()
|
||||
Clear()
|
||||
|
||||
bool IsScrolledDown()
|
||||
|
||||
FadeOutBackground(float duration)
|
||||
FadeInBackground(float duration)
|
||||
FadeOutText(float duration)
|
||||
FadeInText(float duration)
|
||||
|
||||
### ChatBar
|
||||
The chat bar object handles text entry.
|
||||
|
||||
### Methods
|
||||
TextBox GetTextBox()
|
||||
TextButton GetMessageModeTextButton
|
||||
|
||||
bool IsFocused()
|
||||
CaptureFocus()
|
||||
ReleaseFocus(bool submitted)
|
||||
|
||||
ResetText()
|
||||
SetText(text)
|
||||
|
||||
bool GetEnabled()
|
||||
SetEnabled(bool enabled)
|
||||
|
||||
SetTextLabelText(string text)
|
||||
SetTextBoxText(string text)
|
||||
string GetTextBoxText()
|
||||
|
||||
ResetSize()
|
||||
SetTextSize(int textSize)
|
||||
SetChannelTarget(string channelName)
|
||||
|
||||
FadeOutBackground(float duration)
|
||||
FadeInBackground(float duration)
|
||||
FadeOutText(float duration)
|
||||
FadeInText(float duration)
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
-- // FileName: TransparencyTweener.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Data structure for tweening transparency of a group of objects as one unit.
|
||||
|
||||
local module = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local modulesFolder = script.Parent
|
||||
|
||||
--// Can't use ClassMaker in it's current state since this uses custom __index and __newindex.
|
||||
--// Maybe in the future I'll expand that to be more powerful, but it's alright like this for now.
|
||||
|
||||
--////////////////////////////// Details
|
||||
--//////////////////////////////////////
|
||||
local metatable = {}
|
||||
metatable.__ClassName = "TransparencyTweener"
|
||||
|
||||
metatable.__tostring = function(tbl)
|
||||
return tbl.__ClassName .. ": " .. tbl.MemoryLocation
|
||||
end
|
||||
|
||||
metatable.__metatable = "The metatable is locked"
|
||||
metatable.__index = function(tbl, index, value)
|
||||
if rawget(tbl, index) then return rawget(tbl, index) end
|
||||
if rawget(metatable, index) then return rawget(metatable, index) end
|
||||
|
||||
if (index == "Transparency") then
|
||||
return rawget(tbl, "InternalLastTweenPercentage")
|
||||
end
|
||||
|
||||
error(index .. " is not a valid member of " .. tbl.__ClassName)
|
||||
end
|
||||
metatable.__newindex = function(tbl, index, value)
|
||||
if (index == "Transparency") then
|
||||
tbl.InternalLastTweenPercentage = value
|
||||
tbl:SetPropertiesToTweenPercentage(value)
|
||||
return
|
||||
end
|
||||
|
||||
error(index .. " is not a valid member of " .. tbl.__ClassName)
|
||||
end
|
||||
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
function metatable:Dump()
|
||||
local str = tostring(self)
|
||||
|
||||
for tweenObject, objectProperties in pairs(self.TweenObjects) do
|
||||
local addStr = " | "
|
||||
if (type(tweenObject) == "table") then
|
||||
addStr = addStr .. "{{" .. tweenObject:Dump() .. "}}"
|
||||
elseif (type(tweenObject) == "userdata") then
|
||||
addStr = addStr .. tweenObject.Name .. "/" .. (tweenObject.Parent and tweenObject.Parent.Name or nil)
|
||||
end
|
||||
|
||||
for propertyName, baseValue in pairs(objectProperties) do
|
||||
addStr = addStr .. " [" .. propertyName .. "=" .. string.sub(tostring(baseValue), 1, 4) .. "]"
|
||||
end
|
||||
|
||||
str = str .. addStr
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
function metatable:OutputTest()
|
||||
print("Test Output for {={" .. self:Dump() .. "}=}")
|
||||
for tweenObject, objectProperties in pairs(self.TweenObjects) do
|
||||
print("TweenObject:", tweenObject)
|
||||
for propertyName, baseValue in pairs(objectProperties) do
|
||||
print("\t[" .. propertyName .. "=" .. string.sub(tostring(baseValue), 1, 4) .. "] Actual:" .. tweenObject[propertyName])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function metatable:RegisterTweenObjectProperty(objectValue, propertyName, baseValue)
|
||||
baseValue = baseValue or objectValue[propertyName]
|
||||
|
||||
if (not self.TweenObjects[objectValue]) then
|
||||
self.TweenObjects[objectValue] = {}
|
||||
end
|
||||
|
||||
self.TweenObjects[objectValue][propertyName] = baseValue
|
||||
|
||||
self:SetObjectPropertyToPercentValue(objectValue, propertyName, baseValue, self.InternalLastTweenPercentage)
|
||||
end
|
||||
|
||||
function metatable:UnregisterTweenObject(objectValue)
|
||||
self.TweenObjects[objectValue] = nil
|
||||
end
|
||||
|
||||
function metatable:SetObjectPropertyToPercentValue(objectValue, propertyName, baseValue, percentValue)
|
||||
local tweenOverValue = 1 - baseValue
|
||||
local actualValue = baseValue + (tweenOverValue * percentValue)
|
||||
objectValue[propertyName] = actualValue
|
||||
end
|
||||
|
||||
function metatable:SetPropertiesToTweenPercentage(percentValue)
|
||||
for tweenObject, objectProperties in pairs(self.TweenObjects) do
|
||||
for propertyName, baseValue in pairs(objectProperties) do
|
||||
self:SetObjectPropertyToPercentValue(tweenObject, propertyName, baseValue, percentValue)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function metatable:Tween(duration, targetPercentage, startingPercentage)
|
||||
if (self.Tweening) then
|
||||
self.QueuedTween = {duration, targetPercentage, startingPercentage}
|
||||
self.TweenIsQueued = true
|
||||
self:CancelTween()
|
||||
return
|
||||
end
|
||||
self.Tweening = true
|
||||
|
||||
local vStartingPercentage = startingPercentage
|
||||
|
||||
if (vStartingPercentage) then
|
||||
self:SetPropertiesToTweenPercentage(vStartingPercentage)
|
||||
else
|
||||
vStartingPercentage = self.InternalLastTweenPercentage
|
||||
end
|
||||
|
||||
local startTime = tick()
|
||||
local endTime = startTime + duration
|
||||
local tweeningOverPercentage = targetPercentage - vStartingPercentage
|
||||
|
||||
local percentComplete = 0
|
||||
spawn(function()
|
||||
local now = tick()
|
||||
while(now < endTime and not self.Canceled) do
|
||||
percentComplete = math.min(math.max((now - startTime) / duration, 0), 1)
|
||||
local percentValue = vStartingPercentage + (tweeningOverPercentage * percentComplete)
|
||||
self.InternalLastTweenPercentage = percentValue
|
||||
self:SetPropertiesToTweenPercentage(percentValue)
|
||||
|
||||
RunService.RenderStepped:wait()
|
||||
now = tick()
|
||||
end
|
||||
|
||||
if (not self.Canceled) then
|
||||
self.InternalLastTweenPercentage = targetPercentage
|
||||
self:SetPropertiesToTweenPercentage(targetPercentage)
|
||||
|
||||
else
|
||||
self.Canceled = false
|
||||
|
||||
if (self.TweenIsQueued) then
|
||||
self.TweenIsQueued = false
|
||||
self.Tweening = false
|
||||
self:Tween(unpack(self.QueuedTween))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.Tweening = false
|
||||
end)
|
||||
end
|
||||
|
||||
function metatable:CancelTween()
|
||||
self.Canceled = true
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
function module.new()
|
||||
local obj = {}
|
||||
obj.MemoryLocation = tostring(obj):match("[0123456789ABCDEF]+")
|
||||
|
||||
--// We do not want to hold strong references to objects.
|
||||
obj.TweenObjects = setmetatable({}, {__mode = "k"})
|
||||
|
||||
--// Transparency is a property that doesn't actually exist.
|
||||
--// The index of 'Transparency' is used for reading from and
|
||||
--// writing to InternalLastTweenPercentage. This needs to be
|
||||
--// done through metatables so we can also get the behavior
|
||||
--// of calling the method 'SetPropertiesToTweenPercentage'
|
||||
--// automatically when a new value is set.
|
||||
|
||||
obj.Transparency = nil
|
||||
obj.InternalLastTweenPercentage = 0
|
||||
obj.Tweening = false
|
||||
obj.Canceled = false
|
||||
|
||||
obj.TweenIsQueued = false
|
||||
obj.QueuedTween = {}
|
||||
|
||||
obj = setmetatable(obj, metatable)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,472 @@
|
||||
------------------------------------------------------------------------
|
||||
-- Freecam
|
||||
-- Cinematic free camera for spectating and video production.
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local pi = math.pi
|
||||
local abs = math.abs
|
||||
local clamp = math.clamp
|
||||
local exp = math.exp
|
||||
local rad = math.rad
|
||||
local sign = math.sign
|
||||
local sqrt = math.sqrt
|
||||
local tan = math.tan
|
||||
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
if not LocalPlayer then
|
||||
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
|
||||
LocalPlayer = Players.LocalPlayer
|
||||
end
|
||||
|
||||
local Camera = workspace.CurrentCamera
|
||||
workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
|
||||
local newCamera = workspace.CurrentCamera
|
||||
if newCamera then
|
||||
Camera = newCamera
|
||||
end
|
||||
end)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local TOGGLE_INPUT_PRIORITY = Enum.ContextActionPriority.Low.Value
|
||||
local INPUT_PRIORITY = Enum.ContextActionPriority.High.Value
|
||||
local FREECAM_MACRO_KB = {Enum.KeyCode.LeftShift, Enum.KeyCode.P}
|
||||
|
||||
local NAV_GAIN = Vector3.new(1, 1, 1)*64
|
||||
local PAN_GAIN = Vector2.new(0.75, 1)*8
|
||||
local FOV_GAIN = 300
|
||||
|
||||
local PITCH_LIMIT = rad(90)
|
||||
|
||||
local VEL_STIFFNESS = 1.5
|
||||
local PAN_STIFFNESS = 1.0
|
||||
local FOV_STIFFNESS = 4.0
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local Spring = {} do
|
||||
Spring.__index = Spring
|
||||
|
||||
function Spring.new(freq, pos)
|
||||
local self = setmetatable({}, Spring)
|
||||
self.f = freq
|
||||
self.p = pos
|
||||
self.v = pos*0
|
||||
return self
|
||||
end
|
||||
|
||||
function Spring:Update(dt, goal)
|
||||
local f = self.f*2*pi
|
||||
local p0 = self.p
|
||||
local v0 = self.v
|
||||
|
||||
local offset = goal - p0
|
||||
local decay = exp(-f*dt)
|
||||
|
||||
local p1 = goal + (v0*dt - offset*(f*dt + 1))*decay
|
||||
local v1 = (f*dt*(offset*f - v0) + v0)*decay
|
||||
|
||||
self.p = p1
|
||||
self.v = v1
|
||||
|
||||
return p1
|
||||
end
|
||||
|
||||
function Spring:Reset(pos)
|
||||
self.p = pos
|
||||
self.v = pos*0
|
||||
end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local cameraPos = Vector3.new()
|
||||
local cameraRot = Vector2.new()
|
||||
local cameraFov = 0
|
||||
|
||||
local velSpring = Spring.new(VEL_STIFFNESS, Vector3.new())
|
||||
local panSpring = Spring.new(PAN_STIFFNESS, Vector2.new())
|
||||
local fovSpring = Spring.new(FOV_STIFFNESS, 0)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local Input = {} do
|
||||
local thumbstickCurve do
|
||||
local K_CURVATURE = 2.0
|
||||
local K_DEADZONE = 0.15
|
||||
|
||||
local function fCurve(x)
|
||||
return (exp(K_CURVATURE*x) - 1)/(exp(K_CURVATURE) - 1)
|
||||
end
|
||||
|
||||
local function fDeadzone(x)
|
||||
return fCurve((x - K_DEADZONE)/(1 - K_DEADZONE))
|
||||
end
|
||||
|
||||
function thumbstickCurve(x)
|
||||
return sign(x)*clamp(fDeadzone(abs(x)), 0, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local gamepad = {
|
||||
ButtonX = 0,
|
||||
ButtonY = 0,
|
||||
DPadDown = 0,
|
||||
DPadUp = 0,
|
||||
ButtonL2 = 0,
|
||||
ButtonR2 = 0,
|
||||
Thumbstick1 = Vector2.new(),
|
||||
Thumbstick2 = Vector2.new(),
|
||||
}
|
||||
|
||||
local keyboard = {
|
||||
W = 0,
|
||||
A = 0,
|
||||
S = 0,
|
||||
D = 0,
|
||||
E = 0,
|
||||
Q = 0,
|
||||
U = 0,
|
||||
H = 0,
|
||||
J = 0,
|
||||
K = 0,
|
||||
I = 0,
|
||||
Y = 0,
|
||||
Up = 0,
|
||||
Down = 0,
|
||||
LeftShift = 0,
|
||||
RightShift = 0,
|
||||
}
|
||||
|
||||
local mouse = {
|
||||
Delta = Vector2.new(),
|
||||
MouseWheel = 0,
|
||||
}
|
||||
|
||||
local NAV_GAMEPAD_SPEED = Vector3.new(1, 1, 1)
|
||||
local NAV_KEYBOARD_SPEED = Vector3.new(1, 1, 1)
|
||||
local PAN_MOUSE_SPEED = Vector2.new(1, 1)*(pi/64)
|
||||
local PAN_GAMEPAD_SPEED = Vector2.new(1, 1)*(pi/8)
|
||||
local FOV_WHEEL_SPEED = 1.0
|
||||
local FOV_GAMEPAD_SPEED = 0.25
|
||||
local NAV_ADJ_SPEED = 0.75
|
||||
local NAV_SHIFT_MUL = 0.25
|
||||
|
||||
local navSpeed = 1
|
||||
|
||||
function Input.Vel(dt)
|
||||
navSpeed = clamp(navSpeed + dt*(keyboard.Up - keyboard.Down)*NAV_ADJ_SPEED, 0.01, 4)
|
||||
|
||||
local kGamepad = Vector3.new(
|
||||
thumbstickCurve(gamepad.Thumbstick1.x),
|
||||
thumbstickCurve(gamepad.ButtonR2) - thumbstickCurve(gamepad.ButtonL2),
|
||||
thumbstickCurve(-gamepad.Thumbstick1.y)
|
||||
)*NAV_GAMEPAD_SPEED
|
||||
|
||||
local kKeyboard = Vector3.new(
|
||||
keyboard.D - keyboard.A + keyboard.K - keyboard.H,
|
||||
keyboard.E - keyboard.Q + keyboard.I - keyboard.Y,
|
||||
keyboard.S - keyboard.W + keyboard.J - keyboard.U
|
||||
)*NAV_KEYBOARD_SPEED
|
||||
|
||||
local shift = UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) or UserInputService:IsKeyDown(Enum.KeyCode.RightShift)
|
||||
|
||||
return (kGamepad + kKeyboard)*(navSpeed*(shift and NAV_SHIFT_MUL or 1))
|
||||
end
|
||||
|
||||
function Input.Pan(dt)
|
||||
local kGamepad = Vector2.new(
|
||||
thumbstickCurve(gamepad.Thumbstick2.y),
|
||||
thumbstickCurve(-gamepad.Thumbstick2.x)
|
||||
)*PAN_GAMEPAD_SPEED
|
||||
local kMouse = mouse.Delta*PAN_MOUSE_SPEED
|
||||
mouse.Delta = Vector2.new()
|
||||
return kGamepad + kMouse
|
||||
end
|
||||
|
||||
function Input.Fov(dt)
|
||||
local kGamepad = (gamepad.ButtonX - gamepad.ButtonY)*FOV_GAMEPAD_SPEED
|
||||
local kMouse = mouse.MouseWheel*FOV_WHEEL_SPEED
|
||||
mouse.MouseWheel = 0
|
||||
return kGamepad + kMouse
|
||||
end
|
||||
|
||||
do
|
||||
local function Keypress(action, state, input)
|
||||
keyboard[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function GpButton(action, state, input)
|
||||
gamepad[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function MousePan(action, state, input)
|
||||
local delta = input.Delta
|
||||
mouse.Delta = Vector2.new(-delta.y, -delta.x)
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function Thumb(action, state, input)
|
||||
gamepad[input.KeyCode.Name] = input.Position
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function Trigger(action, state, input)
|
||||
gamepad[input.KeyCode.Name] = input.Position.z
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function MouseWheel(action, state, input)
|
||||
mouse[input.UserInputType.Name] = -input.Position.z
|
||||
return Enum.ContextActionResult.Sink
|
||||
end
|
||||
|
||||
local function Zero(t)
|
||||
for k, v in pairs(t) do
|
||||
t[k] = v*0
|
||||
end
|
||||
end
|
||||
|
||||
function Input.StartCapture()
|
||||
ContextActionService:BindActionAtPriority("FreecamKeyboard", Keypress, false, INPUT_PRIORITY,
|
||||
Enum.KeyCode.W, Enum.KeyCode.U,
|
||||
Enum.KeyCode.A, Enum.KeyCode.H,
|
||||
Enum.KeyCode.S, Enum.KeyCode.J,
|
||||
Enum.KeyCode.D, Enum.KeyCode.K,
|
||||
Enum.KeyCode.E, Enum.KeyCode.I,
|
||||
Enum.KeyCode.Q, Enum.KeyCode.Y,
|
||||
Enum.KeyCode.Up, Enum.KeyCode.Down
|
||||
)
|
||||
ContextActionService:BindActionAtPriority("FreecamMousePan", MousePan, false, INPUT_PRIORITY, Enum.UserInputType.MouseMovement)
|
||||
ContextActionService:BindActionAtPriority("FreecamMouseWheel", MouseWheel, false, INPUT_PRIORITY, Enum.UserInputType.MouseWheel)
|
||||
ContextActionService:BindActionAtPriority("FreecamGamepadButton", GpButton, false, INPUT_PRIORITY, Enum.KeyCode.ButtonX, Enum.KeyCode.ButtonY)
|
||||
ContextActionService:BindActionAtPriority("FreecamGamepadTrigger", Trigger, false, INPUT_PRIORITY, Enum.KeyCode.ButtonR2, Enum.KeyCode.ButtonL2)
|
||||
ContextActionService:BindActionAtPriority("FreecamGamepadThumbstick", Thumb, false, INPUT_PRIORITY, Enum.KeyCode.Thumbstick1, Enum.KeyCode.Thumbstick2)
|
||||
end
|
||||
|
||||
function Input.StopCapture()
|
||||
navSpeed = 1
|
||||
Zero(gamepad)
|
||||
Zero(keyboard)
|
||||
Zero(mouse)
|
||||
ContextActionService:UnbindAction("FreecamKeyboard")
|
||||
ContextActionService:UnbindAction("FreecamMousePan")
|
||||
ContextActionService:UnbindAction("FreecamMouseWheel")
|
||||
ContextActionService:UnbindAction("FreecamGamepadButton")
|
||||
ContextActionService:UnbindAction("FreecamGamepadTrigger")
|
||||
ContextActionService:UnbindAction("FreecamGamepadThumbstick")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function GetFocusDistance(cameraFrame)
|
||||
local znear = 0.1
|
||||
local viewport = Camera.ViewportSize
|
||||
local projy = 2*tan(cameraFov/2)
|
||||
local projx = viewport.x/viewport.y*projy
|
||||
local fx = cameraFrame.rightVector
|
||||
local fy = cameraFrame.upVector
|
||||
local fz = cameraFrame.lookVector
|
||||
|
||||
local minVect = Vector3.new()
|
||||
local minDist = 512
|
||||
|
||||
for x = 0, 1, 0.5 do
|
||||
for y = 0, 1, 0.5 do
|
||||
local cx = (x - 0.5)*projx
|
||||
local cy = (y - 0.5)*projy
|
||||
local offset = fx*cx - fy*cy + fz
|
||||
local origin = cameraFrame.p + offset*znear
|
||||
local part, hit = workspace:FindPartOnRay(Ray.new(origin, offset.unit*minDist))
|
||||
local dist = (hit - origin).magnitude
|
||||
if minDist > dist then
|
||||
minDist = dist
|
||||
minVect = offset.unit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return fz:Dot(minVect)*minDist
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function StepFreecam(dt)
|
||||
local vel = velSpring:Update(dt, Input.Vel(dt))
|
||||
local pan = panSpring:Update(dt, Input.Pan(dt))
|
||||
local fov = fovSpring:Update(dt, Input.Fov(dt))
|
||||
|
||||
local zoomFactor = sqrt(tan(rad(70/2))/tan(rad(cameraFov/2)))
|
||||
|
||||
cameraFov = clamp(cameraFov + fov*FOV_GAIN*(dt/zoomFactor), 1, 120)
|
||||
cameraRot = cameraRot + pan*PAN_GAIN*(dt/zoomFactor)
|
||||
cameraRot = Vector2.new(clamp(cameraRot.x, -PITCH_LIMIT, PITCH_LIMIT), cameraRot.y%(2*pi))
|
||||
|
||||
local cameraCFrame = CFrame.new(cameraPos)*CFrame.fromOrientation(cameraRot.x, cameraRot.y, 0)*CFrame.new(vel*NAV_GAIN*dt)
|
||||
cameraPos = cameraCFrame.p
|
||||
|
||||
Camera.CFrame = cameraCFrame
|
||||
Camera.Focus = cameraCFrame*CFrame.new(0, 0, -GetFocusDistance(cameraCFrame))
|
||||
Camera.FieldOfView = cameraFov
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local PlayerState = {} do
|
||||
local mouseIconEnabled
|
||||
local cameraSubject
|
||||
local cameraType
|
||||
local cameraFocus
|
||||
local cameraCFrame
|
||||
local cameraFieldOfView
|
||||
local screenGuis = {}
|
||||
local coreGuis = {
|
||||
Backpack = true,
|
||||
Chat = true,
|
||||
Health = true,
|
||||
PlayerList = true,
|
||||
}
|
||||
local setCores = {
|
||||
BadgesNotificationsActive = true,
|
||||
PointsNotificationsActive = true,
|
||||
}
|
||||
|
||||
-- Save state and set up for freecam
|
||||
function PlayerState.Push()
|
||||
for name in pairs(coreGuis) do
|
||||
coreGuis[name] = StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType[name])
|
||||
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType[name], false)
|
||||
end
|
||||
for name in pairs(setCores) do
|
||||
setCores[name] = StarterGui:GetCore(name)
|
||||
StarterGui:SetCore(name, false)
|
||||
end
|
||||
local playergui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
|
||||
if playergui then
|
||||
for _, gui in pairs(playergui:GetChildren()) do
|
||||
if gui:IsA("ScreenGui") and gui.Enabled then
|
||||
screenGuis[#screenGuis + 1] = gui
|
||||
gui.Enabled = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cameraFieldOfView = Camera.FieldOfView
|
||||
Camera.FieldOfView = 70
|
||||
|
||||
cameraType = Camera.CameraType
|
||||
Camera.CameraType = Enum.CameraType.Custom
|
||||
|
||||
cameraSubject = Camera.CameraSubject
|
||||
Camera.CameraSubject = nil
|
||||
|
||||
cameraCFrame = Camera.CFrame
|
||||
cameraFocus = Camera.Focus
|
||||
|
||||
mouseIconEnabled = UserInputService.MouseIconEnabled
|
||||
UserInputService.MouseIconEnabled = false
|
||||
|
||||
mouseBehavior = UserInputService.MouseBehavior
|
||||
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
|
||||
end
|
||||
|
||||
-- Restore state
|
||||
function PlayerState.Pop()
|
||||
for name, isEnabled in pairs(coreGuis) do
|
||||
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType[name], isEnabled)
|
||||
end
|
||||
for name, isEnabled in pairs(setCores) do
|
||||
StarterGui:SetCore(name, isEnabled)
|
||||
end
|
||||
for _, gui in pairs(screenGuis) do
|
||||
if gui.Parent then
|
||||
gui.Enabled = true
|
||||
end
|
||||
end
|
||||
|
||||
Camera.FieldOfView = cameraFieldOfView
|
||||
cameraFieldOfView = nil
|
||||
|
||||
Camera.CameraType = cameraType
|
||||
cameraType = nil
|
||||
|
||||
Camera.CameraSubject = cameraSubject
|
||||
cameraSubject = nil
|
||||
|
||||
Camera.CFrame = cameraCFrame
|
||||
cameraCFrame = nil
|
||||
|
||||
Camera.Focus = cameraFocus
|
||||
cameraFocus = nil
|
||||
|
||||
UserInputService.MouseIconEnabled = mouseIconEnabled
|
||||
mouseIconEnabled = nil
|
||||
|
||||
UserInputService.MouseBehavior = mouseBehavior
|
||||
mouseBehavior = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function StartFreecam()
|
||||
local cameraCFrame = Camera.CFrame
|
||||
cameraRot = Vector2.new(cameraCFrame:toEulerAnglesYXZ())
|
||||
cameraPos = cameraCFrame.p
|
||||
cameraFov = Camera.FieldOfView
|
||||
|
||||
velSpring:Reset(Vector3.new())
|
||||
panSpring:Reset(Vector2.new())
|
||||
fovSpring:Reset(0)
|
||||
|
||||
PlayerState.Push()
|
||||
RunService:BindToRenderStep("Freecam", Enum.RenderPriority.Camera.Value, StepFreecam)
|
||||
Input.StartCapture()
|
||||
end
|
||||
|
||||
local function StopFreecam()
|
||||
Input.StopCapture()
|
||||
RunService:UnbindFromRenderStep("Freecam")
|
||||
PlayerState.Pop()
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
do
|
||||
local enabled = false
|
||||
|
||||
local function ToggleFreecam()
|
||||
if enabled then
|
||||
StopFreecam()
|
||||
else
|
||||
StartFreecam()
|
||||
end
|
||||
enabled = not enabled
|
||||
end
|
||||
|
||||
local function CheckMacro(macro)
|
||||
for i = 1, #macro - 1 do
|
||||
if not UserInputService:IsKeyDown(macro[i]) then
|
||||
return
|
||||
end
|
||||
end
|
||||
ToggleFreecam()
|
||||
end
|
||||
|
||||
local function HandleActivationInput(action, state, input)
|
||||
if state == Enum.UserInputState.Begin then
|
||||
if input.KeyCode == FREECAM_MACRO_KB[#FREECAM_MACRO_KB] then
|
||||
CheckMacro(FREECAM_MACRO_KB)
|
||||
end
|
||||
end
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
|
||||
ContextActionService:BindActionAtPriority("FreecamToggle", HandleActivationInput, false, TOGGLE_INPUT_PRIORITY, FREECAM_MACRO_KB[#FREECAM_MACRO_KB])
|
||||
end
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
-- // FileName: FreeCameraInstaller.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Installs the free camera ability for members of the Roblox Admin Group.
|
||||
-- The code is kept on the server and only replicated to the client of members of the Roblox admin group
|
||||
-- to minimize security problems.
|
||||
|
||||
-- Users in the following groups have global Freecam permissions:
|
||||
local FREECAM_GROUP_IDS = {
|
||||
1200769,
|
||||
3013794,
|
||||
4358041,
|
||||
}
|
||||
|
||||
local HttpRbxApiService = game:GetService("HttpRbxApiService")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local freeCameraDevelopersFlagSuccess, freeCameraDevelopersFlagValue = pcall(function() return settings():GetFFlag("FreeCameraForDevelopers") end)
|
||||
local freeCameraDevelopersFlag = freeCameraDevelopersFlagSuccess and freeCameraDevelopersFlagValue
|
||||
|
||||
local function Install()
|
||||
local function AddFreeCamera(player)
|
||||
local playerGui = player:WaitForChild("PlayerGui")
|
||||
local originalModule = script.Parent:WaitForChild("FreeCamera")
|
||||
local script = Instance.new("LocalScript")
|
||||
script.Name = "FreeCamera"
|
||||
script.Source = originalModule.Source
|
||||
script.Parent = playerGui
|
||||
end
|
||||
|
||||
local function ShouldAddFreeCam(player)
|
||||
if player.UserId <= 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
if freeCameraDevelopersFlag then
|
||||
if RunService:IsStudio() then
|
||||
return true
|
||||
end
|
||||
|
||||
local success, result = pcall(function()
|
||||
local url = string.format("/users/%d/canmanage/%d", player.UserId, game.PlaceId)
|
||||
-- API returns: {"Success":BOOLEAN,"CanManage":BOOLEAN}
|
||||
local response = HttpRbxApiService:GetAsync(url, Enum.ThrottlingPriority.Default, Enum.HttpRequestType.Default, true)
|
||||
return HttpService:JSONDecode(response)
|
||||
end)
|
||||
|
||||
if success and result.CanManage == true then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for _, groupId in ipairs(FREECAM_GROUP_IDS) do
|
||||
local success, inGroup = pcall(player.IsInGroup, player, groupId)
|
||||
if success and inGroup then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function PlayerAdded(player)
|
||||
if ShouldAddFreeCam(player) then
|
||||
AddFreeCamera(player)
|
||||
end
|
||||
end
|
||||
|
||||
Players.PlayerAdded:Connect(PlayerAdded)
|
||||
for _, player in ipairs(Players:GetPlayers()) do
|
||||
coroutine.wrap(PlayerAdded)(player) -- PlayerAdded may yield, so wrap it in a coroutine to avoid holding up the thread.
|
||||
end
|
||||
end
|
||||
|
||||
return Install
|
||||
@@ -0,0 +1,719 @@
|
||||
-- // FileName: ChatChannel.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: A representation of one channel that speakers can chat in.
|
||||
|
||||
local forceNewFilterAPI = false
|
||||
local IN_GAME_CHAT_USE_NEW_FILTER_API
|
||||
do
|
||||
local textServiceExists = (game:GetService("TextService") ~= nil)
|
||||
local success, enabled = pcall(function() return UserSettings():IsUserFeatureEnabled("UserInGameChatUseNewFilterAPIV2") end)
|
||||
local flagEnabled = (success and enabled)
|
||||
IN_GAME_CHAT_USE_NEW_FILTER_API = (forceNewFilterAPI or flagEnabled) and textServiceExists
|
||||
end
|
||||
|
||||
local module = {}
|
||||
|
||||
local modulesFolder = script.Parent
|
||||
local Chat = game:GetService("Chat")
|
||||
local RunService = game:GetService("RunService")
|
||||
local replicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local ChatConstants = require(replicatedModules:WaitForChild("ChatConstants"))
|
||||
local Util = require(modulesFolder:WaitForChild("Util"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:SendSystemMessage(message, extraData)
|
||||
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
|
||||
|
||||
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
|
||||
if not success and err then
|
||||
print("Error posting message: " ..err)
|
||||
end
|
||||
|
||||
self:InternalAddMessageToHistoryLog(messageObj)
|
||||
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
speaker:InternalSendSystemMessage(messageObj, self.Name)
|
||||
end
|
||||
|
||||
return messageObj
|
||||
end
|
||||
|
||||
function methods:SendSystemMessageToSpeaker(message, speakerName, extraData)
|
||||
local speaker = self.Speakers[speakerName]
|
||||
if (speaker) then
|
||||
local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)
|
||||
speaker:InternalSendSystemMessage(messageObj, self.Name)
|
||||
else
|
||||
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a system message", speakerName, self.Name))
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SendMessageObjToFilters(message, messageObj, fromSpeaker)
|
||||
local oldMessage = messageObj.Message
|
||||
messageObj.Message = message
|
||||
self:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
|
||||
self.ChatService:InternalDoMessageFilter(fromSpeaker.Name, messageObj, self.Name)
|
||||
local newMessage = messageObj.Message
|
||||
messageObj.Message = oldMessage
|
||||
return newMessage
|
||||
end
|
||||
|
||||
function methods:CanCommunicateByUserId(userId1, userId2)
|
||||
if RunService:IsStudio() then
|
||||
return true
|
||||
end
|
||||
-- UserId is set as 0 for non player speakers.
|
||||
if userId1 == 0 or userId2 == 0 then
|
||||
return true
|
||||
end
|
||||
local success, canCommunicate = pcall(function()
|
||||
return Chat:CanUsersChatAsync(userId1, userId2)
|
||||
end)
|
||||
return success and canCommunicate
|
||||
end
|
||||
|
||||
function methods:CanCommunicate(speakerObj1, speakerObj2)
|
||||
local player1 = speakerObj1:GetPlayer()
|
||||
local player2 = speakerObj2:GetPlayer()
|
||||
if player1 and player2 then
|
||||
return self:CanCommunicateByUserId(player1.UserId, player2.UserId)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function methods:SendMessageToSpeaker(message, speakerName, fromSpeakerName, extraData)
|
||||
local speakerTo = self.Speakers[speakerName]
|
||||
local speakerFrom = self.ChatService:GetSpeaker(fromSpeakerName)
|
||||
if speakerTo and speakerFrom then
|
||||
local isMuted = speakerTo:IsSpeakerMuted(fromSpeakerName)
|
||||
if isMuted then
|
||||
return
|
||||
end
|
||||
|
||||
if not self:CanCommunicate(speakerTo, speakerFrom) then
|
||||
return
|
||||
end
|
||||
|
||||
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
|
||||
local isFiltered = speakerName == fromSpeakerName
|
||||
local messageObj = self:InternalCreateMessageObject(message, fromSpeakerName, isFiltered, extraData)
|
||||
message = self:SendMessageObjToFilters(message, messageObj, fromSpeakerName)
|
||||
speakerTo:InternalSendMessage(messageObj, self.Name)
|
||||
|
||||
--// START FFLAG
|
||||
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
|
||||
--// OLD BEHAVIOR
|
||||
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
|
||||
if filteredMessage then
|
||||
messageObj.Message = filteredMessage
|
||||
messageObj.IsFiltered = true
|
||||
speakerTo:InternalSendFilteredMessage(messageObj, self.Name)
|
||||
end
|
||||
--// OLD BEHAVIOR
|
||||
else
|
||||
--// NEW BEHAVIOR
|
||||
local textContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
|
||||
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
|
||||
messageObj.FromSpeaker,
|
||||
message,
|
||||
textContext
|
||||
)
|
||||
if (filterSuccess) then
|
||||
messageObj.FilterResult = filteredMessage
|
||||
messageObj.IsFilterResult = isFilterResult
|
||||
messageObj.IsFiltered = true
|
||||
speakerTo:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
|
||||
end
|
||||
--// NEW BEHAVIOR
|
||||
end
|
||||
--// END FFLAG
|
||||
else
|
||||
warn(string.format("Speaker '%s' is not in channel '%s' and cannot be sent a message", speakerName, self.Name))
|
||||
end
|
||||
end
|
||||
|
||||
function methods:KickSpeaker(speakerName, reason)
|
||||
local speaker = self.ChatService:GetSpeaker(speakerName)
|
||||
if (not speaker) then
|
||||
error("Speaker \"" .. speakerName .. "\" does not exist!")
|
||||
end
|
||||
|
||||
local messageToSpeaker = ""
|
||||
local messageToChannel = ""
|
||||
|
||||
if (reason) then
|
||||
messageToSpeaker = string.format("You were kicked from '%s' for the following reason(s): %s", self.Name, reason)
|
||||
messageToChannel = string.format("%s was kicked for the following reason(s): %s", speakerName, reason)
|
||||
else
|
||||
messageToSpeaker = string.format("You were kicked from '%s'", self.Name)
|
||||
messageToChannel = string.format("%s was kicked", speakerName)
|
||||
end
|
||||
|
||||
self:SendSystemMessageToSpeaker(messageToSpeaker, speakerName)
|
||||
speaker:LeaveChannel(self.Name)
|
||||
self:SendSystemMessage(messageToChannel)
|
||||
end
|
||||
|
||||
function methods:MuteSpeaker(speakerName, reason, length)
|
||||
local speaker = self.ChatService:GetSpeaker(speakerName)
|
||||
if (not speaker) then
|
||||
error("Speaker \"" .. speakerName .. "\" does not exist!")
|
||||
end
|
||||
|
||||
self.Mutes[speakerName:lower()] = (length == 0 or length == nil) and 0 or (os.time() + length)
|
||||
|
||||
if (reason) then
|
||||
self:SendSystemMessage(string.format("%s was muted for the following reason(s): %s", speakerName, reason))
|
||||
end
|
||||
|
||||
local success, err = pcall(function() self.eSpeakerMuted:Fire(speakerName, reason, length) end)
|
||||
if not success and err then
|
||||
print("Error mutting speaker: " ..err)
|
||||
end
|
||||
|
||||
local spkr = self.ChatService:GetSpeaker(speakerName)
|
||||
if (spkr) then
|
||||
local success, err = pcall(function() spkr.eMuted:Fire(self.Name, reason, length) end)
|
||||
if not success and err then
|
||||
print("Error mutting speaker: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function methods:UnmuteSpeaker(speakerName)
|
||||
local speaker = self.ChatService:GetSpeaker(speakerName)
|
||||
if (not speaker) then
|
||||
error("Speaker \"" .. speakerName .. "\" does not exist!")
|
||||
end
|
||||
|
||||
self.Mutes[speakerName:lower()] = nil
|
||||
|
||||
local success, err = pcall(function() self.eSpeakerUnmuted:Fire(speakerName) end)
|
||||
if not success and err then
|
||||
print("Error unmuting speaker: " ..err)
|
||||
end
|
||||
|
||||
local spkr = self.ChatService:GetSpeaker(speakerName)
|
||||
if (spkr) then
|
||||
local success, err = pcall(function() spkr.eUnmuted:Fire(self.Name) end)
|
||||
if not success and err then
|
||||
print("Error unmuting speaker: " ..err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:IsSpeakerMuted(speakerName)
|
||||
return (self.Mutes[speakerName:lower()] ~= nil)
|
||||
end
|
||||
|
||||
function methods:GetSpeakerList()
|
||||
local list = {}
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
table.insert(list, speaker.Name)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function methods:RegisterFilterMessageFunction(funcId, func, priority)
|
||||
if self.FilterMessageFunctions:HasFunction(funcId) then
|
||||
error(string.format("FilterMessageFunction '%s' already exists", funcId))
|
||||
end
|
||||
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
|
||||
end
|
||||
|
||||
function methods:FilterMessageFunctionExists(funcId)
|
||||
return self.FilterMessageFunctions:HasFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:UnregisterFilterMessageFunction(funcId)
|
||||
if not self.FilterMessageFunctions:HasFunction(funcId) then
|
||||
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
|
||||
end
|
||||
self.FilterMessageFunctions:RemoveFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
|
||||
if self.ProcessCommandsFunctions:HasFunction(funcId) then
|
||||
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
|
||||
end
|
||||
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
|
||||
end
|
||||
|
||||
function methods:ProcessCommandsFunctionExists(funcId)
|
||||
return self.ProcessCommandsFunctions:HasFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:UnregisterProcessCommandsFunction(funcId)
|
||||
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
|
||||
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
|
||||
end
|
||||
self.ProcessCommandsFunctions:RemoveFunction(funcId)
|
||||
end
|
||||
|
||||
local function ShallowCopy(table)
|
||||
local copy = {}
|
||||
for i, v in pairs(table) do
|
||||
copy[i] = v
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
function methods:GetHistoryLog()
|
||||
return ShallowCopy(self.ChatHistory)
|
||||
end
|
||||
|
||||
function methods:GetHistoryLogForSpeaker(speaker)
|
||||
local userId = -1
|
||||
local player = speaker:GetPlayer()
|
||||
if player then
|
||||
userId = player.UserId
|
||||
end
|
||||
local chatlog = {}
|
||||
--// START FFLAG
|
||||
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
|
||||
--// OLD BEHAVIOR
|
||||
for i = 1, #self.ChatHistory do
|
||||
local logUserId = self.ChatHistory[i].SpeakerUserId
|
||||
if self:CanCommunicateByUserId(userId, logUserId) then
|
||||
table.insert(chatlog, ShallowCopy(self.ChatHistory[i]))
|
||||
end
|
||||
end
|
||||
--// OLD BEHAVIOR
|
||||
else
|
||||
--// NEW BEHAVIOR
|
||||
for i = 1, #self.ChatHistory do
|
||||
local logUserId = self.ChatHistory[i].SpeakerUserId
|
||||
if self:CanCommunicateByUserId(userId, logUserId) then
|
||||
local messageObj = ShallowCopy(self.ChatHistory[i])
|
||||
|
||||
--// Since we're using the new filter API, we need to convert the stored filter result
|
||||
--// into an actual string message to send to players for their chat history.
|
||||
--// System messages aren't filtered the same way, so they just have a regular
|
||||
--// text value in the Message field.
|
||||
if (messageObj.MessageType == ChatConstants.MessageTypeDefault or messageObj.MessageType == ChatConstants.MessageTypeMeCommand) then
|
||||
local filterResult = messageObj.FilterResult
|
||||
if (messageObj.IsFilterResult) then
|
||||
if (player) then
|
||||
messageObj.Message = filterResult:GetChatForUserAsync(player.UserId)
|
||||
else
|
||||
messageObj.Message = filterResult:GetNonChatStringForBroadcastAsync()
|
||||
end
|
||||
else
|
||||
messageObj.Message = filterResult
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(chatlog, messageObj)
|
||||
end
|
||||
end
|
||||
--// NEW BEHAVIOR
|
||||
end
|
||||
--// END FFLAG
|
||||
return chatlog
|
||||
end
|
||||
|
||||
--///////////////// Internal-Use Methods
|
||||
--//////////////////////////////////////
|
||||
function methods:InternalDestroy()
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
speaker:LeaveChannel(self.Name)
|
||||
end
|
||||
|
||||
self.eDestroyed:Fire()
|
||||
|
||||
self.eDestroyed:Destroy()
|
||||
self.eMessagePosted:Destroy()
|
||||
self.eSpeakerJoined:Destroy()
|
||||
self.eSpeakerLeft:Destroy()
|
||||
self.eSpeakerMuted:Destroy()
|
||||
self.eSpeakerUnmuted:Destroy()
|
||||
end
|
||||
|
||||
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
|
||||
local filtersIterator = self.FilterMessageFunctions:GetIterator()
|
||||
for funcId, func, priority in filtersIterator do
|
||||
local success, errorMessage = pcall(function()
|
||||
func(speakerName, messageObj, channel)
|
||||
end)
|
||||
|
||||
if not success then
|
||||
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalDoProcessCommands(speakerName, message, channel)
|
||||
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
|
||||
for funcId, func, priority in commandsIterator do
|
||||
local success, returnValue = pcall(function()
|
||||
local ret = func(speakerName, message, channel)
|
||||
if type(ret) ~= "boolean" then
|
||||
error("Process command functions must return a bool")
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
if not success then
|
||||
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
|
||||
elseif returnValue then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function methods:InternalPostMessage(fromSpeaker, message, extraData)
|
||||
if (self:InternalDoProcessCommands(fromSpeaker.Name, message, self.Name)) then return false end
|
||||
|
||||
if (self.Mutes[fromSpeaker.Name:lower()] ~= nil) then
|
||||
local t = self.Mutes[fromSpeaker.Name:lower()]
|
||||
if (t > 0 and os.time() > t) then
|
||||
self:UnmuteSpeaker(fromSpeaker.Name)
|
||||
else
|
||||
self:SendSystemMessageToSpeaker(ChatLocalization:Get("GameChat_ChatChannel_MutedInChannel","You are muted and cannot talk in this channel"), fromSpeaker.Name)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local messageObj = self:InternalCreateMessageObject(message, fromSpeaker.Name, false, extraData)
|
||||
|
||||
-- allow server to process the unfiltered message string
|
||||
messageObj.Message = message
|
||||
local processedMessage
|
||||
pcall(function()
|
||||
processedMessage = Chat:InvokeChatCallback(Enum.ChatCallbackType.OnServerReceivingMessage, messageObj)
|
||||
end)
|
||||
messageObj.Message = nil
|
||||
|
||||
if processedMessage then
|
||||
|
||||
-- developer server code's choice to mute the message
|
||||
if processedMessage.ShouldDeliver == false then
|
||||
return false
|
||||
end
|
||||
messageObj = processedMessage
|
||||
end
|
||||
|
||||
message = self:SendMessageObjToFilters(message, messageObj, fromSpeaker)
|
||||
|
||||
local sentToList = {}
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
|
||||
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
|
||||
table.insert(sentToList, speaker.Name)
|
||||
if speaker.Name == fromSpeaker.Name then
|
||||
-- Send unfiltered message to speaker who sent the message.
|
||||
local cMessageObj = ShallowCopy(messageObj)
|
||||
cMessageObj.Message = message
|
||||
cMessageObj.IsFiltered = true
|
||||
-- We need to claim the message is filtered even if it not in this case for compatibility with legacy client side code.
|
||||
speaker:InternalSendMessage(cMessageObj, self.Name)
|
||||
else
|
||||
speaker:InternalSendMessage(messageObj, self.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
|
||||
if not success and err then
|
||||
print("Error posting message: " ..err)
|
||||
end
|
||||
|
||||
--// START FFLAG
|
||||
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
|
||||
--// OLD BEHAVIOR
|
||||
local filteredMessages = {}
|
||||
for i, speakerName in pairs(sentToList) do
|
||||
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
|
||||
if filteredMessage then
|
||||
filteredMessages[speakerName] = filteredMessage
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
for i, speakerName in pairs(sentToList) do
|
||||
local speaker = self.Speakers[speakerName]
|
||||
if (speaker) then
|
||||
local cMessageObj = ShallowCopy(messageObj)
|
||||
cMessageObj.Message = filteredMessages[speakerName]
|
||||
cMessageObj.IsFiltered = true
|
||||
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
|
||||
end
|
||||
end
|
||||
|
||||
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message)
|
||||
if filteredMessage then
|
||||
messageObj.Message = filteredMessage
|
||||
else
|
||||
return false
|
||||
end
|
||||
messageObj.IsFiltered = true
|
||||
self:InternalAddMessageToHistoryLog(messageObj)
|
||||
--// OLD BEHAVIOR
|
||||
else
|
||||
--// NEW BEHAVIOR
|
||||
local textFilterContext = self.Private and Enum.TextFilterContext.PrivateChat or Enum.TextFilterContext.PublicChat
|
||||
local filterSuccess, isFilterResult, filteredMessage = self.ChatService:InternalApplyRobloxFilterNewAPI(
|
||||
messageObj.FromSpeaker,
|
||||
message,
|
||||
textFilterContext
|
||||
)
|
||||
if (filterSuccess) then
|
||||
messageObj.FilterResult = filteredMessage
|
||||
messageObj.IsFilterResult = isFilterResult
|
||||
else
|
||||
return false
|
||||
end
|
||||
messageObj.IsFiltered = true
|
||||
self:InternalAddMessageToHistoryLog(messageObj)
|
||||
|
||||
for _, speakerName in pairs(sentToList) do
|
||||
local speaker = self.Speakers[speakerName]
|
||||
if (speaker) then
|
||||
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
|
||||
end
|
||||
end
|
||||
--// NEW BEHAVIOR
|
||||
end
|
||||
--// END FFLAG
|
||||
|
||||
-- One more pass is needed to ensure that no speakers do not recieve the message.
|
||||
-- Otherwise a user could join while the message is being filtered who had not originally been sent the message.
|
||||
local speakersMissingMessage = {}
|
||||
for _, speaker in pairs(self.Speakers) do
|
||||
local isMuted = speaker:IsSpeakerMuted(fromSpeaker.Name)
|
||||
if not isMuted and self:CanCommunicate(fromSpeaker, speaker) then
|
||||
local wasSentMessage = false
|
||||
for _, sentSpeakerName in pairs(sentToList) do
|
||||
if speaker.Name == sentSpeakerName then
|
||||
wasSentMessage = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not wasSentMessage then
|
||||
table.insert(speakersMissingMessage, speaker.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--// START FFLAG
|
||||
if (not IN_GAME_CHAT_USE_NEW_FILTER_API) then --// USES FFLAG
|
||||
--// OLD BEHAVIOR
|
||||
for _, speakerName in pairs(speakersMissingMessage) do
|
||||
local speaker = self.Speakers[speakerName]
|
||||
if speaker then
|
||||
local filteredMessage = self.ChatService:InternalApplyRobloxFilter(messageObj.FromSpeaker, message, speakerName)
|
||||
if filteredMessage == nil then
|
||||
return false
|
||||
end
|
||||
local cMessageObj = ShallowCopy(messageObj)
|
||||
cMessageObj.Message = filteredMessage
|
||||
cMessageObj.IsFiltered = true
|
||||
speaker:InternalSendFilteredMessage(cMessageObj, self.Name)
|
||||
end
|
||||
end
|
||||
--// OLD BEHAVIOR
|
||||
else
|
||||
--// NEW BEHAVIOR
|
||||
for _, speakerName in pairs(speakersMissingMessage) do
|
||||
local speaker = self.Speakers[speakerName]
|
||||
if speaker then
|
||||
speaker:InternalSendFilteredMessageWithFilterResult(messageObj, self.Name)
|
||||
end
|
||||
end
|
||||
--// NEW BEHAVIOR
|
||||
end
|
||||
--// END FFLAG
|
||||
|
||||
return messageObj
|
||||
end
|
||||
|
||||
function methods:InternalAddSpeaker(speaker)
|
||||
if (self.Speakers[speaker.Name]) then
|
||||
warn("Speaker \"" .. speaker.name .. "\" is already in the channel!")
|
||||
return
|
||||
end
|
||||
|
||||
self.Speakers[speaker.Name] = speaker
|
||||
local success, err = pcall(function() self.eSpeakerJoined:Fire(speaker.Name) end)
|
||||
if not success and err then
|
||||
print("Error removing channel: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalRemoveSpeaker(speaker)
|
||||
if (not self.Speakers[speaker.Name]) then
|
||||
warn("Speaker \"" .. speaker.name .. "\" is not in the channel!")
|
||||
return
|
||||
end
|
||||
|
||||
self.Speakers[speaker.Name] = nil
|
||||
local success, err = pcall(function() self.eSpeakerLeft:Fire(speaker.Name) end)
|
||||
if not success and err then
|
||||
print("Error removing speaker: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalRemoveExcessMessagesFromLog()
|
||||
local remove = table.remove
|
||||
while (#self.ChatHistory > self.MaxHistory) do
|
||||
remove(self.ChatHistory, 1)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalAddMessageToHistoryLog(messageObj)
|
||||
table.insert(self.ChatHistory, messageObj)
|
||||
|
||||
self:InternalRemoveExcessMessagesFromLog()
|
||||
end
|
||||
|
||||
function methods:GetMessageType(message, fromSpeaker)
|
||||
if fromSpeaker == nil then
|
||||
return ChatConstants.MessageTypeSystem
|
||||
end
|
||||
return ChatConstants.MessageTypeDefault
|
||||
end
|
||||
|
||||
function methods:InternalCreateMessageObject(message, fromSpeaker, isFiltered, extraData)
|
||||
local messageType = self:GetMessageType(message, fromSpeaker)
|
||||
|
||||
local speakerUserId = -1
|
||||
local speaker = nil
|
||||
|
||||
if fromSpeaker then
|
||||
speaker = self.Speakers[fromSpeaker]
|
||||
if speaker then
|
||||
local player = speaker:GetPlayer()
|
||||
if player then
|
||||
speakerUserId = player.UserId
|
||||
else
|
||||
speakerUserId = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local messageObj =
|
||||
{
|
||||
ID = self.ChatService:InternalGetUniqueMessageId(),
|
||||
FromSpeaker = fromSpeaker,
|
||||
SpeakerUserId = speakerUserId,
|
||||
OriginalChannel = self.Name,
|
||||
MessageLength = string.len(message),
|
||||
MessageType = messageType,
|
||||
IsFiltered = isFiltered,
|
||||
Message = isFiltered and message or nil,
|
||||
--// These two get set by the new API. The comments are just here
|
||||
--// to remind readers that they will exist so it's not super
|
||||
--// confusing if they find them in the code but cannot find them
|
||||
--// here.
|
||||
--FilterResult = nil,
|
||||
--IsFilterResult = false,
|
||||
Time = os.time(),
|
||||
ExtraData = {},
|
||||
}
|
||||
|
||||
if speaker then
|
||||
for k, v in pairs(speaker.ExtraData) do
|
||||
messageObj.ExtraData[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
if (extraData) then
|
||||
for k, v in pairs(extraData) do
|
||||
messageObj.ExtraData[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
return messageObj
|
||||
end
|
||||
|
||||
function methods:SetChannelNameColor(color)
|
||||
self.ChannelNameColor = color
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
speaker:UpdateChannelNameColor(self.Name, color)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetWelcomeMessageForSpeaker(speaker)
|
||||
if self.GetWelcomeMessageFunction then
|
||||
local welcomeMessage = self.GetWelcomeMessageFunction(speaker)
|
||||
if welcomeMessage then
|
||||
return welcomeMessage
|
||||
end
|
||||
end
|
||||
return self.WelcomeMessage
|
||||
end
|
||||
|
||||
function methods:RegisterGetWelcomeMessageFunction(func)
|
||||
if type(func) ~= "function" then
|
||||
error("RegisterGetWelcomeMessageFunction must be called with a function.")
|
||||
end
|
||||
self.GetWelcomeMessageFunction = func
|
||||
end
|
||||
|
||||
function methods:UnRegisterGetWelcomeMessageFunction()
|
||||
self.GetWelcomeMessageFunction = nil
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(vChatService, name, welcomeMessage, channelNameColor)
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.ChatService = vChatService
|
||||
|
||||
obj.Name = name
|
||||
obj.WelcomeMessage = welcomeMessage or ""
|
||||
obj.GetWelcomeMessageFunction = nil
|
||||
obj.ChannelNameColor = channelNameColor
|
||||
|
||||
obj.Joinable = true
|
||||
obj.Leavable = true
|
||||
obj.AutoJoin = false
|
||||
obj.Private = false
|
||||
|
||||
obj.Speakers = {}
|
||||
obj.Mutes = {}
|
||||
|
||||
obj.MaxHistory = 200
|
||||
obj.HistoryIndex = 0
|
||||
obj.ChatHistory = {}
|
||||
|
||||
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
|
||||
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
|
||||
|
||||
-- Make sure to destroy added binadable events in the InternalDestroy method.
|
||||
obj.eDestroyed = Instance.new("BindableEvent")
|
||||
obj.eMessagePosted = Instance.new("BindableEvent")
|
||||
obj.eSpeakerJoined = Instance.new("BindableEvent")
|
||||
obj.eSpeakerLeft = Instance.new("BindableEvent")
|
||||
obj.eSpeakerMuted = Instance.new("BindableEvent")
|
||||
obj.eSpeakerUnmuted = Instance.new("BindableEvent")
|
||||
|
||||
obj.MessagePosted = obj.eMessagePosted.Event
|
||||
obj.SpeakerJoined = obj.eSpeakerJoined.Event
|
||||
obj.SpeakerLeft = obj.eSpeakerLeft.Event
|
||||
obj.SpeakerMuted = obj.eSpeakerMuted.Event
|
||||
obj.SpeakerUnmuted = obj.eSpeakerUnmuted.Event
|
||||
obj.Destroyed = obj.eDestroyed.Event
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,453 @@
|
||||
-- // FileName: ChatService.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Manages creating and destroying ChatChannels and Speakers.
|
||||
|
||||
local MAX_FILTER_RETRIES = 3
|
||||
local FILTER_BACKOFF_INTERVALS = {50/1000, 100/1000, 200/1000}
|
||||
local MAX_FILTER_DURATION = 60
|
||||
|
||||
--- Constants used to decide when to notify that the chat filter is having issues filtering messages.
|
||||
local FILTER_NOTIFCATION_THRESHOLD = 3 --Number of notifcation failures before an error message is output.
|
||||
local FILTER_NOTIFCATION_INTERVAL = 60 --Time between error messages.
|
||||
local FILTER_THRESHOLD_TIME = 60 --If there has not been an issue in this many seconds, the count of issues resets.
|
||||
|
||||
local module = {}
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
|
||||
local modulesFolder = script.Parent
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
|
||||
local errorExtraData = {ChatColor = errorTextColor}
|
||||
|
||||
--////////////////////////////// Include
|
||||
--//////////////////////////////////////
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local ChatChannel = require(modulesFolder:WaitForChild("ChatChannel"))
|
||||
local Speaker = require(modulesFolder:WaitForChild("Speaker"))
|
||||
local Util = require(modulesFolder:WaitForChild("Util"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:AddChannel(channelName, autoJoin)
|
||||
if (self.ChatChannels[channelName:lower()]) then
|
||||
error(string.format("Channel %q alrady exists.", channelName))
|
||||
end
|
||||
|
||||
local function DefaultChannelCommands(fromSpeaker, message)
|
||||
if (message:lower() == "/leave") then
|
||||
local channel = self:GetChannel(channelName)
|
||||
local speaker = self:GetSpeaker(fromSpeaker)
|
||||
if (channel and speaker) then
|
||||
if (channel.Leavable) then
|
||||
speaker:LeaveChannel(channelName)
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatService_YouHaveLeftChannel",
|
||||
string.format("You have left channel '%s'", channelName)
|
||||
),
|
||||
"{RBX_NAME}",channelName),
|
||||
"System"
|
||||
)
|
||||
else
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatService_CannotLeaveChannel","You cannot leave this channel."), channelName)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local channel = ChatChannel.new(self, channelName)
|
||||
self.ChatChannels[channelName:lower()] = channel
|
||||
|
||||
channel:RegisterProcessCommandsFunction("default_commands", DefaultChannelCommands, ChatConstants.HighPriority)
|
||||
|
||||
local success, err = pcall(function() self.eChannelAdded:Fire(channelName) end)
|
||||
if not success and err then
|
||||
print("Error addding channel: " ..err)
|
||||
end
|
||||
|
||||
if autoJoin ~= nil then
|
||||
channel.AutoJoin = autoJoin
|
||||
if autoJoin then
|
||||
for _, speaker in pairs(self.Speakers) do
|
||||
speaker:JoinChannel(channelName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return channel
|
||||
end
|
||||
|
||||
function methods:RemoveChannel(channelName)
|
||||
if (self.ChatChannels[channelName:lower()]) then
|
||||
local n = self.ChatChannels[channelName:lower()].Name
|
||||
|
||||
self.ChatChannels[channelName:lower()]:InternalDestroy()
|
||||
self.ChatChannels[channelName:lower()] = nil
|
||||
|
||||
local success, err = pcall(function() self.eChannelRemoved:Fire(n) end)
|
||||
if not success and err then
|
||||
print("Error removing channel: " ..err)
|
||||
end
|
||||
else
|
||||
warn(string.format("Channel %q does not exist.", channelName))
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetChannel(channelName)
|
||||
return self.ChatChannels[channelName:lower()]
|
||||
end
|
||||
|
||||
|
||||
function methods:AddSpeaker(speakerName)
|
||||
if (self.Speakers[speakerName:lower()]) then
|
||||
error("Speaker \"" .. speakerName .. "\" already exists!")
|
||||
end
|
||||
|
||||
local speaker = Speaker.new(self, speakerName)
|
||||
self.Speakers[speakerName:lower()] = speaker
|
||||
|
||||
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
|
||||
if not success and err then
|
||||
print("Error adding speaker: " ..err)
|
||||
end
|
||||
|
||||
return speaker
|
||||
end
|
||||
|
||||
function methods:InternalUnmuteSpeaker(speakerName)
|
||||
for channelName, channel in pairs(self.ChatChannels) do
|
||||
if channel:IsSpeakerMuted(speakerName) then
|
||||
channel:UnmuteSpeaker(speakerName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RemoveSpeaker(speakerName)
|
||||
if (self.Speakers[speakerName:lower()]) then
|
||||
local n = self.Speakers[speakerName:lower()].Name
|
||||
self:InternalUnmuteSpeaker(n)
|
||||
|
||||
self.Speakers[speakerName:lower()]:InternalDestroy()
|
||||
self.Speakers[speakerName:lower()] = nil
|
||||
|
||||
local success, err = pcall(function() self.eSpeakerRemoved:Fire(n) end)
|
||||
if not success and err then
|
||||
print("Error removing speaker: " ..err)
|
||||
end
|
||||
|
||||
else
|
||||
warn("Speaker \"" .. speakerName .. "\" does not exist!")
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetSpeaker(speakerName)
|
||||
return self.Speakers[speakerName:lower()]
|
||||
end
|
||||
|
||||
function methods:GetChannelList()
|
||||
local list = {}
|
||||
for i, channel in pairs(self.ChatChannels) do
|
||||
if (not channel.Private) then
|
||||
table.insert(list, channel.Name)
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function methods:GetAutoJoinChannelList()
|
||||
local list = {}
|
||||
for i, channel in pairs(self.ChatChannels) do
|
||||
if channel.AutoJoin then
|
||||
table.insert(list, channel)
|
||||
end
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function methods:GetSpeakerList()
|
||||
local list = {}
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
table.insert(list, speaker.Name)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function methods:SendGlobalSystemMessage(message)
|
||||
for i, speaker in pairs(self.Speakers) do
|
||||
speaker:SendSystemMessage(message, nil)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:RegisterFilterMessageFunction(funcId, func, priority)
|
||||
if self.FilterMessageFunctions:HasFunction(funcId) then
|
||||
error(string.format("FilterMessageFunction '%s' already exists", funcId))
|
||||
end
|
||||
self.FilterMessageFunctions:AddFunction(funcId, func, priority)
|
||||
end
|
||||
|
||||
function methods:FilterMessageFunctionExists(funcId)
|
||||
return self.FilterMessageFunctions:HasFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:UnregisterFilterMessageFunction(funcId)
|
||||
if not self.FilterMessageFunctions:HasFunction(funcId) then
|
||||
error(string.format("FilterMessageFunction '%s' does not exists", funcId))
|
||||
end
|
||||
self.FilterMessageFunctions:RemoveFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:RegisterProcessCommandsFunction(funcId, func, priority)
|
||||
if self.ProcessCommandsFunctions:HasFunction(funcId) then
|
||||
error(string.format("ProcessCommandsFunction '%s' already exists", funcId))
|
||||
end
|
||||
self.ProcessCommandsFunctions:AddFunction(funcId, func, priority)
|
||||
end
|
||||
|
||||
function methods:ProcessCommandsFunctionExists(funcId)
|
||||
return self.ProcessCommandsFunctions:HasFunction(funcId)
|
||||
end
|
||||
|
||||
function methods:UnregisterProcessCommandsFunction(funcId)
|
||||
if not self.ProcessCommandsFunctions:HasFunction(funcId) then
|
||||
error(string.format("ProcessCommandsFunction '%s' does not exist", funcId))
|
||||
end
|
||||
self.ProcessCommandsFunctions:RemoveFunction(funcId)
|
||||
end
|
||||
|
||||
local LastFilterNoficationTime = 0
|
||||
local LastFilterIssueTime = 0
|
||||
local FilterIssueCount = 0
|
||||
function methods:InternalNotifyFilterIssue()
|
||||
if (tick() - LastFilterIssueTime) > FILTER_THRESHOLD_TIME then
|
||||
FilterIssueCount = 0
|
||||
end
|
||||
FilterIssueCount = FilterIssueCount + 1
|
||||
LastFilterIssueTime = tick()
|
||||
if FilterIssueCount >= FILTER_NOTIFCATION_THRESHOLD then
|
||||
if (tick() - LastFilterNoficationTime) > FILTER_NOTIFCATION_INTERVAL then
|
||||
LastFilterNoficationTime = tick()
|
||||
local systemChannel = self:GetChannel("System")
|
||||
if systemChannel then
|
||||
systemChannel:SendSystemMessage(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatService_ChatFilterIssues",
|
||||
"The chat filter is currently experiencing issues and messages may be slow to appear."
|
||||
),
|
||||
errorExtraData
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local StudioMessageFilteredCache = {}
|
||||
|
||||
--///////////////// Internal-Use Methods
|
||||
--//////////////////////////////////////
|
||||
--DO NOT REMOVE THIS. Chat must be filtered or your game will face
|
||||
--moderation.
|
||||
function methods:InternalApplyRobloxFilter(speakerName, message, toSpeakerName) --// USES FFLAG
|
||||
if (RunService:IsServer() and not RunService:IsStudio()) then
|
||||
local fromSpeaker = self:GetSpeaker(speakerName)
|
||||
local toSpeaker = toSpeakerName and self:GetSpeaker(toSpeakerName)
|
||||
|
||||
if fromSpeaker == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local fromPlayerObj = fromSpeaker:GetPlayer()
|
||||
local toPlayerObj = toSpeaker and toSpeaker:GetPlayer()
|
||||
|
||||
if fromPlayerObj == nil then
|
||||
return message
|
||||
end
|
||||
|
||||
local filterStartTime = tick()
|
||||
local filterRetries = 0
|
||||
while true do
|
||||
local success, message = pcall(function()
|
||||
if toPlayerObj then
|
||||
return Chat:FilterStringAsync(message, fromPlayerObj, toPlayerObj)
|
||||
else
|
||||
return Chat:FilterStringForBroadcast(message, fromPlayerObj)
|
||||
end
|
||||
end)
|
||||
if success then
|
||||
return message
|
||||
else
|
||||
warn("Error filtering message:", message)
|
||||
end
|
||||
filterRetries = filterRetries + 1
|
||||
if filterRetries > MAX_FILTER_RETRIES or (tick() - filterStartTime) > MAX_FILTER_DURATION then
|
||||
self:InternalNotifyFilterIssue()
|
||||
return nil
|
||||
end
|
||||
local backoffInterval = FILTER_BACKOFF_INTERVALS[math.min(#FILTER_BACKOFF_INTERVALS, filterRetries)]
|
||||
-- backoffWait = backoffInterval +/- (0 -> backoffInterval)
|
||||
local backoffWait = backoffInterval + ((math.random()*2 - 1) * backoffInterval)
|
||||
wait(backoffWait)
|
||||
end
|
||||
else
|
||||
--// Simulate filtering latency.
|
||||
--// There is only latency the first time the message is filtered, all following calls will be instant.
|
||||
if not StudioMessageFilteredCache[message] then
|
||||
StudioMessageFilteredCache[message] = true
|
||||
wait()
|
||||
end
|
||||
return message
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--// Return values: bool filterSuccess, bool resultIsFilterObject, variant result
|
||||
function methods:InternalApplyRobloxFilterNewAPI(speakerName, message, textFilterContext) --// USES FFLAG
|
||||
local alwaysRunFilter = false
|
||||
local runFilter = RunService:IsServer() and not RunService:IsStudio()
|
||||
if (alwaysRunFilter or runFilter) then
|
||||
local fromSpeaker = self:GetSpeaker(speakerName)
|
||||
if fromSpeaker == nil then
|
||||
return false, nil, nil
|
||||
end
|
||||
|
||||
local fromPlayerObj = fromSpeaker:GetPlayer()
|
||||
if fromPlayerObj == nil then
|
||||
return true, false, message
|
||||
end
|
||||
|
||||
local success, filterResult = pcall(function()
|
||||
local ts = game:GetService("TextService")
|
||||
local result = ts:FilterStringAsync(message, fromPlayerObj.UserId, textFilterContext)
|
||||
return result
|
||||
end)
|
||||
if (success) then
|
||||
return true, true, filterResult
|
||||
else
|
||||
warn("Error filtering message:", message, filterResult)
|
||||
self:InternalNotifyFilterIssue()
|
||||
return false, nil, nil
|
||||
end
|
||||
end
|
||||
|
||||
--// Simulate filtering latency.
|
||||
wait()
|
||||
return true, false, message
|
||||
end
|
||||
|
||||
function methods:InternalDoMessageFilter(speakerName, messageObj, channel)
|
||||
local filtersIterator = self.FilterMessageFunctions:GetIterator()
|
||||
|
||||
for funcId, func, priority in filtersIterator do
|
||||
local success, errorMessage = pcall(function()
|
||||
func(speakerName, messageObj, channel)
|
||||
end)
|
||||
|
||||
if not success then
|
||||
warn(string.format("DoMessageFilter Function '%s' failed for reason: %s", funcId, errorMessage))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalDoProcessCommands(speakerName, message, channel)
|
||||
local commandsIterator = self.ProcessCommandsFunctions:GetIterator()
|
||||
|
||||
for funcId, func, priority in commandsIterator do
|
||||
local success, returnValue = pcall(function()
|
||||
local ret = func(speakerName, message, channel)
|
||||
if type(ret) ~= "boolean" then
|
||||
error("Process command functions must return a bool")
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
if not success then
|
||||
warn(string.format("DoProcessCommands Function '%s' failed for reason: %s", funcId, returnValue))
|
||||
elseif returnValue then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function methods:InternalGetUniqueMessageId()
|
||||
local id = self.MessageIdCounter
|
||||
self.MessageIdCounter = id + 1
|
||||
return id
|
||||
end
|
||||
|
||||
function methods:InternalAddSpeakerWithPlayerObject(speakerName, playerObj, fireSpeakerAdded)
|
||||
if (self.Speakers[speakerName:lower()]) then
|
||||
error("Speaker \"" .. speakerName .. "\" already exists!")
|
||||
end
|
||||
|
||||
local speaker = Speaker.new(self, speakerName)
|
||||
speaker:InternalAssignPlayerObject(playerObj)
|
||||
self.Speakers[speakerName:lower()] = speaker
|
||||
|
||||
if fireSpeakerAdded then
|
||||
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
|
||||
if not success and err then
|
||||
print("Error adding speaker: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
return speaker
|
||||
end
|
||||
|
||||
function methods:InternalFireSpeakerAdded(speakerName)
|
||||
local success, err = pcall(function() self.eSpeakerAdded:Fire(speakerName) end)
|
||||
if not success and err then
|
||||
print("Error firing speaker added: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.MessageIdCounter = 0
|
||||
|
||||
obj.ChatChannels = {}
|
||||
obj.Speakers = {}
|
||||
|
||||
obj.FilterMessageFunctions = Util:NewSortedFunctionContainer()
|
||||
obj.ProcessCommandsFunctions = Util:NewSortedFunctionContainer()
|
||||
|
||||
obj.eChannelAdded = Instance.new("BindableEvent")
|
||||
obj.eChannelRemoved = Instance.new("BindableEvent")
|
||||
obj.eSpeakerAdded = Instance.new("BindableEvent")
|
||||
obj.eSpeakerRemoved = Instance.new("BindableEvent")
|
||||
|
||||
obj.ChannelAdded = obj.eChannelAdded.Event
|
||||
obj.ChannelRemoved = obj.eChannelRemoved.Event
|
||||
obj.SpeakerAdded = obj.eSpeakerAdded.Event
|
||||
obj.SpeakerRemoved = obj.eSpeakerRemoved.Event
|
||||
|
||||
obj.ChatServiceMajorVersion = 0
|
||||
obj.ChatServiceMinorVersion = 5
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module.new()
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
local runnerScriptName = "ChatServiceRunner"
|
||||
|
||||
local installDirectory = game:GetService("Chat")
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local ChatServiceInstallerExemptLocalizationFromAnalytics = settings():GetFFlag("ChatServiceInstallerExemptLocalizationFromAnalytics")
|
||||
|
||||
local function LoadScript(name, parent)
|
||||
local originalModule = script.Parent:WaitForChild(name)
|
||||
local script = Instance.new("Script")
|
||||
script.Name = name
|
||||
script.Source = originalModule.Source
|
||||
script.Parent = parent
|
||||
return script
|
||||
end
|
||||
|
||||
local function LoadModule(location, name, parent)
|
||||
local originalModule = location:WaitForChild(name)
|
||||
local module = Instance.new("ModuleScript")
|
||||
module.Name = name
|
||||
module.Source = originalModule.Source
|
||||
module.Parent = parent
|
||||
return module
|
||||
end
|
||||
|
||||
local function GetBoolValue(parent, name, defaultValue)
|
||||
local boolValue = parent:FindFirstChild(name)
|
||||
if boolValue then
|
||||
if boolValue:IsA("BoolValue") then
|
||||
return boolValue.Value
|
||||
end
|
||||
end
|
||||
return defaultValue
|
||||
end
|
||||
|
||||
local function makeDefaultLocalizationTable(parent)
|
||||
local defaultChatLocalization = Instance.new("LocalizationTable")
|
||||
defaultChatLocalization.Name = "ChatLocalization"
|
||||
defaultChatLocalization.Archivable = false
|
||||
defaultChatLocalization.SourceLocaleId = "en-us"
|
||||
defaultChatLocalization:SetEntries(require(script.Parent:WaitForChild("DefaultChatLocalization")))
|
||||
defaultChatLocalization:SetIsExemptFromUGCAnalytics(true)
|
||||
defaultChatLocalization.Parent = parent;
|
||||
end
|
||||
|
||||
local function Install()
|
||||
local existingChatLocalization
|
||||
|
||||
if ChatServiceInstallerExemptLocalizationFromAnalytics then
|
||||
existingChatLocalization = installDirectory:FindFirstChild("ChatLocalization")
|
||||
if existingChatLocalization then
|
||||
if existingChatLocalization:IsA("LocalizationTable" ) then
|
||||
existingChatLocalization:SetIsExemptFromUGCAnalytics(true)
|
||||
end
|
||||
else
|
||||
makeDefaultLocalizationTable(installDirectory)
|
||||
end
|
||||
else
|
||||
if not installDirectory:FindFirstChild("ChatLocalization") then
|
||||
local defaultChatLocalization = Instance.new("LocalizationTable")
|
||||
defaultChatLocalization.Name = "ChatLocalization"
|
||||
defaultChatLocalization.Archivable = false
|
||||
defaultChatLocalization.SourceLocaleId = "en-us"
|
||||
defaultChatLocalization:SetEntries(require(script.Parent:WaitForChild("DefaultChatLocalization")))
|
||||
defaultChatLocalization.Parent = installDirectory
|
||||
end
|
||||
end
|
||||
|
||||
local chatServiceRunnerArchivable = true
|
||||
local ChatServiceRunner = installDirectory:FindFirstChild(runnerScriptName)
|
||||
if not ChatServiceRunner then
|
||||
chatServiceRunnerArchivable = false
|
||||
ChatServiceRunner = LoadScript(runnerScriptName, installDirectory)
|
||||
|
||||
LoadModule(script.Parent, "ChatService", ChatServiceRunner)
|
||||
LoadModule(script.Parent, "ChatChannel", ChatServiceRunner)
|
||||
LoadModule(script.Parent, "Speaker", ChatServiceRunner)
|
||||
LoadModule(script.Parent, "Util", ChatServiceRunner)
|
||||
end
|
||||
|
||||
local ChatModules = installDirectory:FindFirstChild("ChatModules")
|
||||
if not ChatModules then
|
||||
ChatModules = Instance.new("Folder")
|
||||
ChatModules.Name = "ChatModules"
|
||||
ChatModules.Archivable = false
|
||||
|
||||
local InsertDefaults = Instance.new("BoolValue")
|
||||
InsertDefaults.Name = "InsertDefaultModules"
|
||||
InsertDefaults.Value = true
|
||||
InsertDefaults.Parent = ChatModules
|
||||
|
||||
ChatModules.Parent = installDirectory
|
||||
end
|
||||
|
||||
local shouldInsertDefaultModules = GetBoolValue(ChatModules, "InsertDefaultModules", false)
|
||||
|
||||
if shouldInsertDefaultModules then
|
||||
local defaultChatModules = script.Parent.DefaultChatModules:GetChildren()
|
||||
for i = 1, #defaultChatModules do
|
||||
if not ChatModules:FindFirstChild(defaultChatModules[i].Name) then
|
||||
LoadModule(script.Parent.DefaultChatModules, defaultChatModules[i].Name, ChatModules)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not ServerScriptService:FindFirstChild(runnerScriptName) then
|
||||
local ChatServiceRunnerCopy = ChatServiceRunner:Clone()
|
||||
ChatServiceRunnerCopy.Archivable = false
|
||||
ChatServiceRunnerCopy.Parent = ServerScriptService
|
||||
end
|
||||
|
||||
ChatServiceRunner.Archivable = chatServiceRunnerArchivable
|
||||
end
|
||||
|
||||
return Install
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
-- // FileName: ChatServiceRunner.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Main script to initialize ChatService and run ChatModules.
|
||||
|
||||
local EventFolderName = "DefaultChatSystemChatEvents"
|
||||
local EventFolderParent = game:GetService("ReplicatedStorage")
|
||||
local modulesFolder = script
|
||||
|
||||
local PlayersService = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local Chat = game:GetService("Chat")
|
||||
|
||||
local ChatService = require(modulesFolder:WaitForChild("ChatService"))
|
||||
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(Chat.ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
|
||||
|
||||
local useEvents = {}
|
||||
|
||||
local EventFolder = EventFolderParent:FindFirstChild(EventFolderName)
|
||||
if (not EventFolder) then
|
||||
EventFolder = Instance.new("Folder")
|
||||
EventFolder.Name = EventFolderName
|
||||
EventFolder.Archivable = false
|
||||
EventFolder.Parent = EventFolderParent
|
||||
end
|
||||
|
||||
--// No-opt connect Server>Client RemoteEvents to ensure they cannot be called
|
||||
--// to fill the remote event queue.
|
||||
local function emptyFunction()
|
||||
--intentially empty
|
||||
end
|
||||
|
||||
local function GetObjectWithNameAndType(parentObject, objectName, objectType)
|
||||
for _, child in pairs(parentObject:GetChildren()) do
|
||||
if (child:IsA(objectType) and child.Name == objectName) then
|
||||
return child
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function CreateIfDoesntExist(parentObject, objectName, objectType)
|
||||
local obj = GetObjectWithNameAndType(parentObject, objectName, objectType)
|
||||
if (not obj) then
|
||||
obj = Instance.new(objectType)
|
||||
obj.Name = objectName
|
||||
obj.Parent = parentObject
|
||||
end
|
||||
useEvents[objectName] = obj
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
--// All remote events will have a no-opt OnServerEvent connecdted on construction
|
||||
local function CreateEventIfItDoesntExist(parentObject, objectName)
|
||||
local obj = CreateIfDoesntExist(parentObject, objectName, "RemoteEvent")
|
||||
obj.OnServerEvent:Connect(emptyFunction)
|
||||
return obj
|
||||
end
|
||||
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnNewMessage")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnMessageDoneFiltering")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnNewSystemMessage")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnChannelJoined")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnChannelLeft")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnMuted")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnUnmuted")
|
||||
CreateEventIfItDoesntExist(EventFolder, "OnMainChannelSet")
|
||||
CreateEventIfItDoesntExist(EventFolder, "ChannelNameColorUpdated")
|
||||
|
||||
CreateEventIfItDoesntExist(EventFolder, "SayMessageRequest")
|
||||
CreateEventIfItDoesntExist(EventFolder, "SetBlockedUserIdsRequest")
|
||||
CreateIfDoesntExist(EventFolder, "GetInitDataRequest", "RemoteFunction")
|
||||
CreateIfDoesntExist(EventFolder, "MutePlayerRequest", "RemoteFunction")
|
||||
CreateIfDoesntExist(EventFolder, "UnMutePlayerRequest", "RemoteFunction")
|
||||
|
||||
EventFolder = useEvents
|
||||
|
||||
local function CreatePlayerSpeakerObject(playerObj)
|
||||
--// If a developer already created a speaker object with the
|
||||
--// name of a player and then a player joins and tries to
|
||||
--// take that name, we first need to remove the old speaker object
|
||||
local speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
if (speaker) then
|
||||
ChatService:RemoveSpeaker(playerObj.Name)
|
||||
end
|
||||
|
||||
speaker = ChatService:InternalAddSpeakerWithPlayerObject(playerObj.Name, playerObj, false)
|
||||
|
||||
for _, channel in pairs(ChatService:GetAutoJoinChannelList()) do
|
||||
speaker:JoinChannel(channel.Name)
|
||||
end
|
||||
|
||||
speaker:InternalAssignEventFolder(EventFolder)
|
||||
|
||||
speaker.ChannelJoined:connect(function(channel, welcomeMessage)
|
||||
local log = nil
|
||||
local channelNameColor = nil
|
||||
|
||||
local channelObject = ChatService:GetChannel(channel)
|
||||
if (channelObject) then
|
||||
log = channelObject:GetHistoryLogForSpeaker(speaker)
|
||||
channelNameColor = channelObject.ChannelNameColor
|
||||
end
|
||||
EventFolder.OnChannelJoined:FireClient(playerObj, channel, welcomeMessage, log, channelNameColor)
|
||||
end)
|
||||
|
||||
speaker.Muted:connect(function(channel, reason, length)
|
||||
EventFolder.OnMuted:FireClient(playerObj, channel, reason, length)
|
||||
end)
|
||||
|
||||
speaker.Unmuted:connect(function(channel)
|
||||
EventFolder.OnUnmuted:FireClient(playerObj, channel)
|
||||
end)
|
||||
|
||||
ChatService:InternalFireSpeakerAdded(speaker.Name)
|
||||
end
|
||||
|
||||
EventFolder.SayMessageRequest.OnServerEvent:connect(function(playerObj, message, channel)
|
||||
if type(message) ~= "string" then
|
||||
return
|
||||
end
|
||||
if type(channel) ~= "string" then
|
||||
return
|
||||
end
|
||||
|
||||
local speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
if (speaker) then
|
||||
return speaker:SayMessage(message, channel)
|
||||
end
|
||||
|
||||
return nil
|
||||
end)
|
||||
|
||||
EventFolder.MutePlayerRequest.OnServerInvoke = function(playerObj, muteSpeakerName)
|
||||
if type(muteSpeakerName) ~= "string" then
|
||||
return
|
||||
end
|
||||
|
||||
local speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
if speaker then
|
||||
local muteSpeaker = ChatService:GetSpeaker(muteSpeakerName)
|
||||
if muteSpeaker then
|
||||
speaker:AddMutedSpeaker(muteSpeaker.Name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
EventFolder.UnMutePlayerRequest.OnServerInvoke = function(playerObj, unmuteSpeakerName)
|
||||
if type(unmuteSpeakerName) ~= "string" then
|
||||
return
|
||||
end
|
||||
|
||||
local speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
if speaker then
|
||||
local unmuteSpeaker = ChatService:GetSpeaker(unmuteSpeakerName)
|
||||
if unmuteSpeaker then
|
||||
speaker:RemoveMutedSpeaker(unmuteSpeaker.Name)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Map storing Player -> Blocked user Ids.
|
||||
local BlockedUserIdsMap = {}
|
||||
|
||||
PlayersService.PlayerAdded:connect(function(newPlayer)
|
||||
for player, blockedUsers in pairs(BlockedUserIdsMap) do
|
||||
local speaker = ChatService:GetSpeaker(player.Name)
|
||||
if speaker then
|
||||
for i = 1, #blockedUsers do
|
||||
local blockedUserId = blockedUsers[i]
|
||||
if blockedUserId == newPlayer.UserId then
|
||||
speaker:AddMutedSpeaker(newPlayer.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
PlayersService.PlayerRemoving:connect(function(removingPlayer)
|
||||
BlockedUserIdsMap[removingPlayer] = nil
|
||||
end)
|
||||
|
||||
EventFolder.SetBlockedUserIdsRequest.OnServerEvent:connect(function(player, blockedUserIdsList)
|
||||
if type(blockedUserIdsList) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
BlockedUserIdsMap[player] = blockedUserIdsList
|
||||
local speaker = ChatService:GetSpeaker(player.Name)
|
||||
if speaker then
|
||||
for i = 1, #blockedUserIdsList do
|
||||
if type(blockedUserIdsList[i]) == "number" then
|
||||
local blockedPlayer = PlayersService:GetPlayerByUserId(blockedUserIdsList[i])
|
||||
if blockedPlayer then
|
||||
speaker:AddMutedSpeaker(blockedPlayer.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
EventFolder.GetInitDataRequest.OnServerInvoke = (function(playerObj)
|
||||
local speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
if not (speaker and speaker:GetPlayer()) then
|
||||
CreatePlayerSpeakerObject(playerObj)
|
||||
speaker = ChatService:GetSpeaker(playerObj.Name)
|
||||
end
|
||||
|
||||
local data = {}
|
||||
data.Channels = {}
|
||||
data.SpeakerExtraData = {}
|
||||
|
||||
for _, channelName in pairs(speaker:GetChannelList()) do
|
||||
local channelObj = ChatService:GetChannel(channelName)
|
||||
if (channelObj) then
|
||||
local channelData =
|
||||
{
|
||||
channelName,
|
||||
channelObj:GetWelcomeMessageForSpeaker(speaker),
|
||||
channelObj:GetHistoryLogForSpeaker(speaker),
|
||||
channelObj.ChannelNameColor,
|
||||
}
|
||||
|
||||
table.insert(data.Channels, channelData)
|
||||
end
|
||||
end
|
||||
|
||||
for _, oSpeakerName in pairs(ChatService:GetSpeakerList()) do
|
||||
local oSpeaker = ChatService:GetSpeaker(oSpeakerName)
|
||||
data.SpeakerExtraData[oSpeakerName] = oSpeaker.ExtraData
|
||||
end
|
||||
|
||||
return data
|
||||
end)
|
||||
|
||||
local function DoJoinCommand(speakerName, channelName, fromChannelName)
|
||||
local speaker = ChatService:GetSpeaker(speakerName)
|
||||
local channel = ChatService:GetChannel(channelName)
|
||||
|
||||
if (speaker) then
|
||||
if (channel) then
|
||||
if (channel.Joinable) then
|
||||
if (not speaker:IsInChannel(channel.Name)) then
|
||||
speaker:JoinChannel(channel.Name)
|
||||
else
|
||||
speaker:SetMainChannel(channel.Name)
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_SwitchChannel_NowInChannel",
|
||||
string.format("You are now chatting in channel: '%s'", channel.Name)
|
||||
),
|
||||
"{RBX_NAME}",channel.Name),
|
||||
channel.Name
|
||||
)
|
||||
end
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatServiceRunner_YouCannotJoinChannel",
|
||||
("You cannot join channel '" .. channelName .. "'.")
|
||||
),
|
||||
"{RBX_NAME}",channelName),
|
||||
fromChannelName
|
||||
)
|
||||
end
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatServiceRunner_ChannelDoesNotExist",
|
||||
("Channel '" .. channelName .. "' does not exist.")
|
||||
),
|
||||
"{RBX_NAME}",channelName),
|
||||
fromChannelName
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function DoLeaveCommand(speakerName, channelName, fromChannelName)
|
||||
local speaker = ChatService:GetSpeaker(speakerName)
|
||||
local channel = ChatService:GetChannel(channelName)
|
||||
|
||||
if (speaker) then
|
||||
if (speaker:IsInChannel(channelName)) then
|
||||
if (channel.Leavable) then
|
||||
speaker:LeaveChannel(channel.Name)
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatService_YouHaveLeftChannel",
|
||||
string.format("You have left channel '%s'", channelName)
|
||||
),
|
||||
"{RBX_NAME}",channel.Name),
|
||||
"System"
|
||||
)
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatServiceRunner_YouCannotLeaveChannel",
|
||||
("You cannot leave channel '" .. channelName .. "'.")
|
||||
),
|
||||
"{RBX_NAME}",channelName),
|
||||
fromChannelName
|
||||
)
|
||||
end
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatServiceRunner_YouAreNotInChannel",
|
||||
("You are not in channel '" .. channelName .. "'.")
|
||||
),
|
||||
"{RBX_NAME}",channelName),
|
||||
fromChannelName
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("default_commands", function(fromSpeaker, message, channel)
|
||||
if (string.sub(message, 1, 6):lower() == "/join ") then
|
||||
DoJoinCommand(fromSpeaker, string.sub(message, 7), channel)
|
||||
return true
|
||||
elseif (string.sub(message, 1, 3):lower() == "/j ") then
|
||||
DoJoinCommand(fromSpeaker, string.sub(message, 4), channel)
|
||||
return true
|
||||
|
||||
elseif (string.sub(message, 1, 7):lower() == "/leave ") then
|
||||
DoLeaveCommand(fromSpeaker, string.sub(message, 8), channel)
|
||||
return true
|
||||
elseif (string.sub(message, 1, 3):lower() == "/l ") then
|
||||
DoLeaveCommand(fromSpeaker, string.sub(message, 4), channel)
|
||||
return true
|
||||
|
||||
elseif (string.sub(message, 1, 3) == "/e " or string.sub(message, 1, 7) == "/emote ") then
|
||||
-- Just don't show these in the chatlog. The animation script listens on these.
|
||||
return true
|
||||
|
||||
end
|
||||
|
||||
return false
|
||||
end)
|
||||
|
||||
if ChatSettings.GeneralChannelName and ChatSettings.GeneralChannelName ~= "" then
|
||||
local allChannel = ChatService:AddChannel(ChatSettings.GeneralChannelName)
|
||||
|
||||
allChannel.Leavable = false
|
||||
allChannel.AutoJoin = true
|
||||
|
||||
allChannel:RegisterGetWelcomeMessageFunction(function(speaker)
|
||||
if RunService:IsStudio() then
|
||||
return nil
|
||||
end
|
||||
local player = speaker:GetPlayer()
|
||||
if player then
|
||||
local success, canChat = pcall(function()
|
||||
return Chat:CanUserChatAsync(player.UserId)
|
||||
end)
|
||||
if success and not canChat then
|
||||
return ""
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local systemChannel = ChatService:AddChannel("System")
|
||||
systemChannel.Leavable = false
|
||||
systemChannel.AutoJoin = true
|
||||
systemChannel.WelcomeMessage = ChatLocalization:Get(
|
||||
"GameChat_ChatServiceRunner_SystemChannelWelcomeMessage", "This channel is for system and game notifications."
|
||||
)
|
||||
|
||||
systemChannel.SpeakerJoined:connect(function(speakerName)
|
||||
systemChannel:MuteSpeaker(speakerName)
|
||||
end)
|
||||
|
||||
|
||||
local function TryRunModule(module)
|
||||
if module:IsA("ModuleScript") then
|
||||
local ret = require(module)
|
||||
if (type(ret) == "function") then
|
||||
ret(ChatService)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local modules = Chat:WaitForChild("ChatModules")
|
||||
modules.ChildAdded:connect(function(child)
|
||||
local success, returnval = pcall(TryRunModule, child)
|
||||
if not success and returnval then
|
||||
print("Error running module " ..child.Name.. ": " ..returnval)
|
||||
end
|
||||
end)
|
||||
|
||||
for _, module in pairs(modules:GetChildren()) do
|
||||
local success, returnval = pcall(TryRunModule, module)
|
||||
if not success and returnval then
|
||||
print("Error running module " ..module.Name.. ": " ..returnval)
|
||||
end
|
||||
end
|
||||
|
||||
PlayersService.PlayerRemoving:connect(function(playerObj)
|
||||
if (ChatService:GetSpeaker(playerObj.Name)) then
|
||||
ChatService:RemoveSpeaker(playerObj.Name)
|
||||
end
|
||||
end)
|
||||
+699
@@ -0,0 +1,699 @@
|
||||
return {
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_AllChannelWelcomeMessage",
|
||||
Values = {
|
||||
["ru"] = "Введите «/?» или «/help», чтобы увидеть список команд чата.",
|
||||
["fr"] = "Dans le chat, « /? » or « /help » pour la liste des commandes du chat.",
|
||||
["en-us"] = "Chat '/?' or '/help' for a list of chat commands.",
|
||||
["de"] = "Gib /? oder /help im Chat ein, um eine Liste der Chatbefehle zu erhalten.",
|
||||
["it"] = "Scrivi \"/?\" o \"/help\" per avere l'elenco dei comandi della chat.",
|
||||
["pt"] = "Digite '/?' ou '/help' no chat para ver uma lista de comandos.",
|
||||
["ja"] = "チャットで '/?' または '/help' を入力するとチャットコマンドの一覧を表示します。",
|
||||
["es"] = "Envía \"/?\" o \"/help\" para obtener la lista de comandos del chat.",
|
||||
["pt-br"] = "Digite '/?' ou '/help' no chat para ver uma lista de comandos.",
|
||||
["ko"] = "채팅창에 '/?' 또는 '/도움말'을 입력하면 채팅 명령어 목록을 볼 수 있어요.",
|
||||
["zh-tw"] = "Chat '/?' 或 '/help' 可取得聊天指令清單。",
|
||||
["zh-cn"] = "Chat '/?' 或 '/help' 可获取聊天指令清单。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_SwallowGuestChat_Message",
|
||||
Values = {
|
||||
["ru"] = "Создайте бесплатную учетную запись, чтобы настроить права доступа в чате!",
|
||||
["fr"] = "Créez un compte gratuit pour accéder aux permissions de chat !",
|
||||
["en-us"] = "Create a free account to get access to chat permissions!",
|
||||
["de"] = "Erstelle ein kostenloses Konto, um Zugriff auf Chatberechtigungen zu erhalten!",
|
||||
["it"] = "Crea un account gratuito per avere accesso ai permessi della chat!",
|
||||
["pt"] = "Crie uma conta grátis para ter acesso a permissões de chat!",
|
||||
["ja"] = "フリーアカウントを作ってチャットを始めましょう!",
|
||||
["es"] = "¡Crea una cuenta gratuita para obtener los permisos de acceso al chat!",
|
||||
["pt-br"] = "Crie uma conta grátis para ter acesso a permissões de chat!",
|
||||
["ko"] = "무료 계정을 생성해 채팅 권한을 이용하세요!",
|
||||
["zh-tw"] = "請建立免費帳戶,以取得聊天權限!",
|
||||
["zh-cn"] = "创建免费帐户以获取聊天权限!",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_SwitchChannelCommand",
|
||||
Values = {
|
||||
["ru"] = "/c <канал> : переключить вкладку в меню каналов.",
|
||||
["fr"] = "/c <canal> : échanger les onglets du menu Canal.",
|
||||
["en-us"] = "/c <channel> : switch channel menu tabs.",
|
||||
["de"] = "/c <Kanal> : Zum Wechseln zwischen Kanalmenüreitern.",
|
||||
["it"] = "/c <canale>: cambia scheda nel menu dei canali.",
|
||||
["pt"] = "/c <canal> : trocar abas de menu de canal.",
|
||||
["ja"] = "/c <channel> : チャンネルメニュータブを切り替える。",
|
||||
["es"] = "/c <canal>: alternar pestañas del menú del chat.",
|
||||
["pt-br"] = "/c <canal> : trocar abas de menu de canal.",
|
||||
["ko"] = "/c <채널> : 채널 메뉴 탭 전환.",
|
||||
["zh-tw"] = "/c <channel> : 切換頻道選單標籤。",
|
||||
["zh-cn"] = "/c <channel> : 切换频道菜单标签。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_MeCommand",
|
||||
Values = {
|
||||
["ru"] = "/me <текст> : сообщить о выполнении какого-либо действия.",
|
||||
["fr"] = "/me <texte> : commande jeu de rôle pour accomplir des actions.",
|
||||
["en-us"] = "/me <text> : roleplaying command for doing actions.",
|
||||
["de"] = "/me <Text> : Rollenspielbefehl für Aktionen.",
|
||||
["it"] = "/me <testo>: comando per descrivere azioni e giocare di ruolo.",
|
||||
["pt"] = "/me <texto> : comando de roleplaying para realizar ações.",
|
||||
["ja"] = "/me <text> : アクションのためのロールプレイングコマンド。",
|
||||
["es"] = "/me <texto>: comando de rol para realizar acciones.",
|
||||
["pt-br"] = "/me <texto> : comando de roleplaying para realizar ações.",
|
||||
["ko"] = "/me <텍스트> : 작업 수행을 위한 역할 놀이 명령어.",
|
||||
["zh-tw"] = "/me <text> : 做動作的角色扮演指令。",
|
||||
["zh-cn"] = "/me <text> : 做动作的角色扮演指令。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_MuteCommand",
|
||||
Values = {
|
||||
["ru"] = "/mute <пользователь> : игнорировать пользователя.",
|
||||
["fr"] = "/mute <interlocuteur> : bâillonne un interlocuteur.",
|
||||
["en-us"] = "/mute <speaker> : mute a speaker.",
|
||||
["de"] = "/mute <Teilnehmer> : Schaltet einen Teilnehmer stumm.",
|
||||
["it"] = "/mute <giocatore>: togli la parola a un giocatore.",
|
||||
["pt"] = "/mute <pessoa> : silenciar uma pessoa.",
|
||||
["ja"] = "/mute <speaker> :相手をミュート。",
|
||||
["es"] = "/mute <usuario>: silenciar a un usuario.",
|
||||
["pt-br"] = "/mute <pessoa> : silenciar uma pessoa.",
|
||||
["ko"] = "/mute <스피커> : 스피커 음소거.",
|
||||
["zh-tw"] = "/mute <speaker> : 將使用者靜音。",
|
||||
["zh-cn"] = "/mute <usuario>:将发言者静音。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_UnMuteCommand",
|
||||
Values = {
|
||||
["ru"] = "/unmute <пользователь> : перестать игнорировать пользователя.",
|
||||
["fr"] = "/unmute <interlocuteur> : retire le bâillon d'un interlocuteur.",
|
||||
["en-us"] = "/unmute <speaker> : unmute a speaker.",
|
||||
["de"] = "/unmute <Teilnehmer> : Hebt Stummschaltung eines Teilnehmers auf.",
|
||||
["it"] = "/unmute <giocatore>: restituisci la parola a un giocatore.",
|
||||
["pt"] = "/unmute <pessoa> : remover silêncio de uma pessoa.",
|
||||
["ja"] = "/unmute <speaker> : 相手のミュートを解除.",
|
||||
["es"] = "/unmute <usuario>: cancelar silencio de un usuario.",
|
||||
["pt-br"] = "/unmute <pessoa> : remover silêncio de uma pessoa.",
|
||||
["ko"] = "/unmute <스피커> : 스피커 음소거 해제.",
|
||||
["zh-tw"] = "/unmute <speaker> : 取消靜音講者。",
|
||||
["zh-cn"] = "/unmute <speaker> : 取消发言者静音。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_WhisperCommand",
|
||||
Values = {
|
||||
["ru"] = "/whisper <пользователь> или /w <пользователь> : открыть канал для личной переписки с пользователем.",
|
||||
["fr"] = "/whisper <interlocuteur> ou /w <interlocuteur> : ouvre un canal de discussion privé avec l'interlocuteur.",
|
||||
["en-us"] = "/whisper <speaker> or /w <speaker> : open private message channel with speaker.",
|
||||
["de"] = "/whisper <Teilnehmer> oder /w <Teilnehmer> : Öffnet privaten Nachrichtenkanal mit Teilnehmer.",
|
||||
["it"] = "/whisper <giocatore> o /w <giocatore>: apri un canale privato con un giocatore.",
|
||||
["pt"] = "/whisper <pessoa> ou /w <pessoa> : abrir um canal de mensagem privada com uma pessoa.",
|
||||
["ja"] = "/whisper <speaker> または /w <speaker> : プライベートメッセージチャネルを開く",
|
||||
["es"] = "/whisper <usuario> o /w <usuario>: abrir canal de mensajes privados con un usuario.",
|
||||
["pt-br"] = "/whisper <pessoa> ou /w <pessoa> : abrir um canal de mensagem privada com uma pessoa.",
|
||||
["ko"] = "/whisper <스피커> 또는 /w <스피커> : 스피커 채널에서 비공개 메시지 열기.",
|
||||
["zh-tw"] = "/whisper <speaker> 或 /w <speaker> : 開啟與講者的私訊頻道。",
|
||||
["zh-cn"] = "/whisper <speaker> 或 /w <speaker> : 打开与发言者的私人消息频道。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_SpeakerHasBeenMuted",
|
||||
Values = {
|
||||
["ru"] = "Пользователь {RBX_NAME} добавлен в список игнорируемых.",
|
||||
["fr"] = "L'interlocuteur {RBX_NAME} a été bâillonné.",
|
||||
["en-us"] = "Speaker '{RBX_NAME}' has been muted.",
|
||||
["de"] = "Teilnehmer „{RBX_NAME}“ wurde stummgeschaltet.",
|
||||
["it"] = "Hai tolto la parola al giocatore \"{RBX_NAME}\".",
|
||||
["pt"] = "{RBX_NAME}' foi silenciado(a).",
|
||||
["ja"] = "{RBX_NAME}' をミュートしました。",
|
||||
["es"] = "Se ha silenciado al usuario \"{RBX_NAME}\".",
|
||||
["pt-br"] = "{RBX_NAME}' foi silenciado(a).",
|
||||
["ko"] = "스피커 '{RBX_NAME}'이(가) 음소거되었어요.",
|
||||
["zh-tw"] = "講者「{RBX_NAME}」已遭靜音。",
|
||||
["zh-cn"] = "发言者“{RBX_NAME}”已被静音。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_SpeakerHasBeenUnMuted",
|
||||
Values = {
|
||||
["ru"] = "Пользователь {RBX_NAME} удален из списка игнорируемых.",
|
||||
["fr"] = "L'interlocuteur {RBX_NAME} n'est plus bâillonné.",
|
||||
["en-us"] = "Speaker '{RBX_NAME}' has been unmuted.",
|
||||
["de"] = "Stummschaltung von Teilnehmer „{RBX_NAME}“ wurde aufgehoben.",
|
||||
["it"] = "Hai restituito la parola al giocatore \"{RBX_NAME}\".",
|
||||
["pt"] = "O silêncio de '{RBX_NAME}' foi removido.",
|
||||
["ja"] = "{RBX_NAME}' のミュートを解除しました。",
|
||||
["es"] = "Se ha cancelado el silencio del usuario \"{RBX_NAME}\".",
|
||||
["pt-br"] = "O silêncio de '{RBX_NAME}' foi removido.",
|
||||
["ko"] = "스피커 '{RBX_NAME}'의 음소거가 해제되었어요.",
|
||||
["zh-tw"] = "講者「{RBX_NAME}」已被取消靜音。",
|
||||
["zh-cn"] = "发言者“{RBX_NAME}”已被取消静音。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_Desc",
|
||||
Values = {
|
||||
["ru"] = "Это основные команды чата.",
|
||||
["fr"] = "Voici les commandes de chat basiques.",
|
||||
["en-us"] = "These are the basic chat commands.",
|
||||
["de"] = "Das sind die grundlegenden Chatbefehle.",
|
||||
["it"] = "Questi sono i comandi base della chat.",
|
||||
["pt"] = "Esses são os comandos de chat básicos.",
|
||||
["ja"] = "これらは基本的なチャットコマンドです。",
|
||||
["es"] = "Estos son los comandos básicos del chat.",
|
||||
["pt-br"] = "Esses são os comandos de chat básicos.",
|
||||
["ko"] = "기본 채팅 명령어에요.",
|
||||
["zh-tw"] = "這些是基本聊天指令。",
|
||||
["zh-cn"] = "这些是基本聊天指令。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_GetVersion_Message",
|
||||
Values = {
|
||||
["ru"] = "Эта игра поддерживает чат версии [{RBX_NUMBER}.{RBX_NUMBER}].",
|
||||
["fr"] = "Le jeu utilise la version de chat [{RBX_NUMBER} {RBX_NUMBER}].",
|
||||
["en-us"] = "This game is running chat version [{RBX_NUMBER}.{RBX_NUMBER}].",
|
||||
["de"] = "Die Chatversion dieses Spiels ist [{RBX_NUMBER}.{RBX_NUMBER}].",
|
||||
["it"] = "Questo gioco usa la versione [{RBX_NUMBER}.{RBX_NUMBER}] della chat.",
|
||||
["pt"] = "Este jogo está rodando a versão de chat [{RBX_NUMBER}.{RBX_NUMBER}].",
|
||||
["ja"] = "このゲームはチャットバージョン [{RBX_NUMBER}.{RBX_NUMBER}]を実行しています。",
|
||||
["es"] = "Este juego utiliza la versión del chat [{RBX_NUMBER} {RBX_NUMBER}].",
|
||||
["pt-br"] = "Este jogo está rodando a versão de chat [{RBX_NUMBER}.{RBX_NUMBER}].",
|
||||
["ko"] = "이 게임은 채팅 버전 [{RBX_NUMBER}.{RBX_NUMBER}]을(를) 실행합니다.",
|
||||
["zh-tw"] = "此遊戲正執行聊天版本 [{RBX_NUMBER}.{RBX_NUMBER}]。",
|
||||
["zh-cn"] = "此游戏正在运行聊天版本 [{RBX_NUMBER}.{RBX_NUMBER}]。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_SwitchChannel_NowInChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы общаетесь на канале «{RBX_NAME}»",
|
||||
["fr"] = "Maintenant, vous discutez sur le canal : {RBX_NAME}",
|
||||
["en-us"] = "You are now chatting in channel: '{RBX_NAME}'",
|
||||
["de"] = "Du chattest jetzt in Kanal „{RBX_NAME}“.",
|
||||
["it"] = "Ora stai parlando nel canale: \"{RBX_NAME}\"",
|
||||
["pt"] = "Você agora está no canal de chat: '{RBX_NAME}'",
|
||||
["ja"] = "あなたの現在のチャットチャンネルは: '{RBX_NAME}' です。",
|
||||
["es"] = "Estás chateando en el canal \"{RBX_NAME}\".",
|
||||
["pt-br"] = "Você agora está no canal de chat: '{RBX_NAME}'",
|
||||
["ko"] = "{RBX_NAME}' 채널에서 채팅 중이에요",
|
||||
["zh-tw"] = "您此刻聊天的頻道在:「{RBX_NAME}」",
|
||||
["zh-cn"] = "你当前的聊天频道为:“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_SwitchChannel_NotInChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы не на канале «{RBX_NAME}»",
|
||||
["fr"] = "Vous n'êtes pas sur le canal : {RBX_NAME}",
|
||||
["en-us"] = "You are not in channel: '{RBX_NAME}'",
|
||||
["de"] = "Du befindest dich nicht in Kanal „{RBX_NAME}“.",
|
||||
["it"] = "Non ti trovi nel canale: \"{RBX_NAME}\"",
|
||||
["pt"] = "Você não está no canal: '{RBX_NAME}'",
|
||||
["ja"] = "あなたはチャネル: '{RBX_NAME}' にいません。",
|
||||
["es"] = "No estás en el canal \"{RBX_NAME}\".",
|
||||
["pt-br"] = "Você não está no canal: '{RBX_NAME}'",
|
||||
["ko"] = "{RBX_NAME}' 채널에 있지 않아요",
|
||||
["zh-tw"] = "您未在頻道:「{RBX_NAME}」",
|
||||
["zh-cn"] = "你不在频道:“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_ChatBarText",
|
||||
Values = {
|
||||
["ru"] = "Чтобы общаться в чате, нажмите здесь или на клавишу «/»",
|
||||
["fr"] = "Pour discuter, cliquez ici ou appuyez sur la touche !",
|
||||
["en-us"] = "To chat click here or press \"/\" key",
|
||||
["de"] = "Klicke zum Chatten hier oder drücke die /-Taste.",
|
||||
["it"] = "Per chattare, clicca qui o premi il tasto \"/\"",
|
||||
["pt"] = "Para escrever clique aqui ou aperte a tecla \"/\"",
|
||||
["ja"] = "チャットするにはここをクリックするか \"/\" キーを押します。",
|
||||
["es"] = "Para chatear, haz clic aquí o pulsa la tecla \"/\".",
|
||||
["pt-br"] = "Para escrever clique aqui ou aperte a tecla \"/\"",
|
||||
["ko"] = "채팅하려면 여기를 클릭하거나 \"/\" 키를 누르세요",
|
||||
["zh-tw"] = "若要聊天,按一下此處或按 \"/\" 鍵",
|
||||
["zh-cn"] = "若要聊天,请点按这里或按住“/”键",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_ChatBarTextTouch",
|
||||
Values = {
|
||||
["ru"] = "Коснитесь здесь, чтобы общаться в чате",
|
||||
["fr"] = "Touchez ici pour discuter",
|
||||
["en-us"] = "Tap here to chat",
|
||||
["de"] = "Tippe zum Chatten hier.",
|
||||
["it"] = "Tocca qui per chattare",
|
||||
["pt"] = "Toque aqui para escrever",
|
||||
["ja"] = "タップしてチャットする",
|
||||
["es"] = "Toca aquí para chatear",
|
||||
["pt-br"] = "Toque aqui para escrever",
|
||||
["ko"] = "채팅하려면 여기를 누르세요",
|
||||
["zh-tw"] = "輕觸此處以聊天",
|
||||
["zh-cn"] = "轻点此处以聊天",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_SpeakerHasBeenBlocked",
|
||||
Values = {
|
||||
["ru"] = "Пользователь {RBX_NAME} заблокирован.",
|
||||
["fr"] = "L'interlocuteur {RBX_NAME} a été bloqué.",
|
||||
["en-us"] = "Speaker '{RBX_NAME}' has been blocked.",
|
||||
["de"] = "Teilnehmer „{RBX_NAME}“ wurde gesperrt.",
|
||||
["it"] = "Hai bloccato il giocatore \"{RBX_NAME}\".",
|
||||
["pt"] = "{RBX_NAME}' foi bloqueado.",
|
||||
["ja"] = "{RBX_NAME}' はブロック中です。",
|
||||
["es"] = "Se ha bloqueado al usuario \"{RBX_NAME}\".",
|
||||
["pt-br"] = "{RBX_NAME}' foi bloqueado.",
|
||||
["ko"] = "스피커 '{RBX_NAME}' 님을 차단했어요.",
|
||||
["zh-tw"] = "講者「{RBX_NAME}」已遭封鎖。",
|
||||
["zh-cn"] = "发言者“{RBX_NAME}”已被屏蔽。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMain_SpeakerHasBeenUnBlocked",
|
||||
Values = {
|
||||
["ru"] = "Пользователь {RBX_NAME} разблокирован.",
|
||||
["fr"] = "L'interlocuteur {RBX_NAME} n'est plus bloqué.",
|
||||
["en-us"] = "Speaker '{RBX_NAME}' has been unblocked.",
|
||||
["de"] = "Sperrung von Teilnehmer „{RBX_NAME}“ wurde aufgehoben.",
|
||||
["it"] = "Hai sbloccato il giocatore \"{RBX_NAME}\".",
|
||||
["pt"] = "{RBX_NAME}' foi desbloqueado.",
|
||||
["ja"] = "{RBX_NAME}' のブロックが解除されました。",
|
||||
["es"] = "Se ha desbloqueado al usuario \"{RBX_NAME}\".",
|
||||
["pt-br"] = "{RBX_NAME}' foi desbloqueado.",
|
||||
["ko"] = "스피커 '{RBX_NAME}' 님 차단을 해제했어요.",
|
||||
["zh-tw"] = "講者「{RBX_NAME}」已被解除封鎖。",
|
||||
["zh-cn"] = "发言者“{RBX_NAME}”已被取消屏蔽。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatCommandsTeller_TeamCommand",
|
||||
Values = {
|
||||
["ru"] = "/team <сообщение> или /t <сообщение> : отправить сообщение игрокам из вашей команды.",
|
||||
["fr"] = "/team <message> ou /t <message> : envoyer un message aux joueurs de votre équipe.",
|
||||
["en-us"] = "/team <message> or /t <message> : send a team chat to players on your team.",
|
||||
["de"] = "/team <Nachricht> oder /t <Nachricht> : Sendet eine Teamnachricht an Spieler deines Teams.",
|
||||
["it"] = "/team <messaggio> o /t <messaggio>: invia un messaggio a tutti i giocatori della tua squadra.",
|
||||
["pt"] = "/team <mensagem> ou /t <mensagem> : enviar um chat de equipe aos jogadores da sua equipe.",
|
||||
["ja"] = "/team <message> または /t <message> : 自分のチームメンバーにチームチャットを送る。",
|
||||
["es"] = "/team <mensaje> o /t <mensaje>: enviar un mensaje de chat de equipo a los jugadores de tu equipo.",
|
||||
["pt-br"] = "/team <mensagem> ou /t <mensagem> : enviar um chat de equipe aos jogadores da sua equipe.",
|
||||
["ko"] = "/team <메시지> 또는 /t <메시지> : 팀 내 플레이어에게 팀 채팅 전송.",
|
||||
["zh-tw"] = "/team <message> 或 /t <message> : 傳送團隊聊天給隊伍中的玩家。",
|
||||
["zh-cn"] = "/team <message> 或 /t <message> : 向你团队的玩家发送团队聊天。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatFloodDetector_MessageDisplaySeconds",
|
||||
Values = {
|
||||
["ru"] = "Вы сможете отправить новое сообщение только через {RBX_NUMBER} сек.!",
|
||||
["fr"] = "Vous devez attendre {RBX_NUMBER} secondes avant d'envoyer un autre message !",
|
||||
["en-us"] = "You must wait {RBX_NUMBER} seconds before sending another message!",
|
||||
["de"] = "Du musst {RBX_NUMBER} Sekunden lang warten, bevor du eine weitere Nachricht senden kannst!",
|
||||
["it"] = "Devi aspettare {RBX_NUMBER} secondi prima di inviare un altro messaggio!",
|
||||
["pt"] = "Você precisa esperar {RBX_NUMBER} segundos antes de enviar outra mensagem!",
|
||||
["ja"] = "{RBX_NUMBER} 秒待ってから次のメッセージを送ってください!",
|
||||
["es"] = "¡Debes esperar {RBX_NUMBER} segundos antes de enviar otro mensaje!",
|
||||
["pt-br"] = "Você precisa esperar {RBX_NUMBER} segundos antes de enviar outra mensagem!",
|
||||
["ko"] = "추가 메시지를 보내기 전에 {RBX_NUMBER}초 동안 기다려야 해요!",
|
||||
["zh-tw"] = "傳送另一則訊息之前您必須等候 {RBX_NUMBER} 秒!",
|
||||
["zh-cn"] = "发送另一条消息前你必须等待 {RBX_NUMBER} 秒!",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatFloodDetector_Message",
|
||||
Values = {
|
||||
["ru"] = "Необходимо подождать, прежде чем отправлять новое сообщение!",
|
||||
["fr"] = "Vous devez attendre avant d'envoyer un autre message !",
|
||||
["en-us"] = "You must wait before sending another message!",
|
||||
["de"] = "Du musst warten, bevor du eine weitere Nachricht senden kannst!",
|
||||
["it"] = "Devi aspettare prima di inviare un altro messaggio!",
|
||||
["pt"] = "Você precisa esperar antes de enviar outra mensagem!",
|
||||
["ja"] = "少し待ってから次のメッセージを送ってください!",
|
||||
["es"] = "¡Debes esperar antes de enviar otro mensaje!",
|
||||
["pt-br"] = "Você precisa esperar antes de enviar outra mensagem!",
|
||||
["ko"] = "추가 메시지를 보내기 전에 기다려야 해요!",
|
||||
["zh-tw"] = "傳送另一則訊息之前您必須等候!",
|
||||
["zh-cn"] = "发送另一条消息前你必须等待!",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMessageValidator_SettingsError",
|
||||
Values = {
|
||||
["ru"] = "В ваших настройках чата заблокирована возможность отправлять сообщения.",
|
||||
["fr"] = "Vos paramètres de chat vous empêchent d'envoyer des messages.",
|
||||
["en-us"] = "Your chat settings prevent you from sending messages.",
|
||||
["de"] = "Aufgrund deiner Chateinstellungen kannst du keine Nachrichten senden.",
|
||||
["it"] = "Non puoi inviare messaggi per le impostazioni della tua chat.",
|
||||
["pt"] = "Suas configurações de chat impedem que você envie mensagens.",
|
||||
["ja"] = "メッセージが送れないチャット設定です。",
|
||||
["es"] = "Tu configuración de chat te impide enviar mensajes.",
|
||||
["pt-br"] = "Suas configurações de chat impedem que você envie mensagens.",
|
||||
["ko"] = "채팅 설정 때문에 메시지를 보낼 수 없어요.",
|
||||
["zh-tw"] = "您的聊天設定禁止您送出訊息。",
|
||||
["zh-cn"] = "你的聊天设置禁止你发送消息。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMessageValidator_MaxLengthError",
|
||||
Values = {
|
||||
["ru"] = "Превышено максимально допустимое количество символов в сообщении.",
|
||||
["fr"] = "Votre message dépasse la longueur maximale.",
|
||||
["en-us"] = "Your message exceeds the maximum message length.",
|
||||
["de"] = "Deine Nachricht überschreitet die zulässige Nachrichtenlänge.",
|
||||
["it"] = "Il tuo messaggio supera la lunghezza massima consentita.",
|
||||
["pt"] = "Sua mensagem ultrapassa o tamanho máximo de mensagem.",
|
||||
["ja"] = "メッセージが最大文字数を超えています。",
|
||||
["es"] = "Tu mensaje supera la longitud máxima permitida.",
|
||||
["pt-br"] = "Sua mensagem ultrapassa o tamanho máximo de mensagem.",
|
||||
["ko"] = "메시지 길이 한도를 초과했어요.",
|
||||
["zh-tw"] = "您的訊息超過最大訊息長度。",
|
||||
["zh-cn"] = "你的消息已超过最大长度限制。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatMessageValidator_WhitespaceError",
|
||||
Values = {
|
||||
["ru"] = "Ваше сообщение содержит недопустимый пробел.",
|
||||
["fr"] = "Votre message contient des espaces blancs qui sont interdits.",
|
||||
["en-us"] = "Your message contains whitespace that is not allowed.",
|
||||
["de"] = "Deine Nachricht enthält unzulässige Leerräume.",
|
||||
["it"] = "Il tuo messaggio contiene spazi vuoti non consentiti.",
|
||||
["pt"] = "Sua mensagem contém um espaço em branco, que não é permitido.",
|
||||
["ja"] = "メッセージに許可されていないスペースが含まれています。",
|
||||
["es"] = "Tu mensaje contiene espacios vacíos que no se permiten.",
|
||||
["pt-br"] = "Sua mensagem contém um espaço em branco, que não é permitido.",
|
||||
["ko"] = "메시지에 허용되지 않는 여백이 있어요.",
|
||||
["zh-tw"] = "您的訊息含有不允許的空格。",
|
||||
["zh-cn"] = "你的消息包含不被允许的空格。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_DoMuteCommand_CannotMuteSelf",
|
||||
Values = {
|
||||
["ru"] = "Невозможно добавить себя в список игнорируемых.",
|
||||
["fr"] = "Vous ne pouvez pas vous bâillonner.",
|
||||
["en-us"] = "You cannot mute yourself.",
|
||||
["de"] = "Du kannst dich nicht selbst stummschalten.",
|
||||
["it"] = "Non puoi togliere la parola a te stesso.",
|
||||
["pt"] = "Você não pode silenciar a si mesmo.",
|
||||
["ja"] = "自分をミュートすることは出来ません。",
|
||||
["es"] = "No puedes silenciarte a ti mismo.",
|
||||
["pt-br"] = "Você não pode silenciar a si mesmo.",
|
||||
["ko"] = "자신을 음소거할 수 없어요.",
|
||||
["zh-tw"] = "您無法將自己靜音。",
|
||||
["zh-cn"] = "你无法将自己静音。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_MuteSpeaker_SpeakerDoesNotExist",
|
||||
Values = {
|
||||
["ru"] = "Пользователя {RBX_NAME} не существует.",
|
||||
["fr"] = "L'interlocuteur {RBX_NAME} n'existe pas.",
|
||||
["en-us"] = "Speaker '{RBX_NAME}' does not exist.",
|
||||
["de"] = "Teilnehmer „{RBX_NAME}“ existiert nicht.",
|
||||
["it"] = "Il giocatore \"{RBX_NAME}\" non esiste.",
|
||||
["pt"] = "{RBX_NAME}' não existe.",
|
||||
["ja"] = "{RBX_NAME}' は存在しません。",
|
||||
["es"] = "El usuario \"{RBX_NAME}\" no existe.",
|
||||
["pt-br"] = "{RBX_NAME}' não existe.",
|
||||
["ko"] = "스피커 '{RBX_NAME}'이(가) 없어요.",
|
||||
["zh-tw"] = "講者「{RBX_NAME}」不存在。",
|
||||
["zh-cn"] = "发言者“{RBX_NAME}”不存在。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_PrivateMessaging_CannotChat",
|
||||
Values = {
|
||||
["ru"] = "Вы не можете общаться в чате с этим игроком.",
|
||||
["fr"] = "Vous ne pouvez pas discuter avec ce joueur.",
|
||||
["en-us"] = "You are not able to chat with this player.",
|
||||
["de"] = "Du kannst mit diesem Spieler nicht chatten.",
|
||||
["it"] = "Non puoi chattare con questo giocatore.",
|
||||
["pt"] = "Você não pode participar de chat com este jogador.",
|
||||
["ja"] = "このプレーヤーとチャットすることは出来ません。",
|
||||
["es"] = "No puedes chatear con este jugador.",
|
||||
["pt-br"] = "Você não pode participar de chat com este jogador.",
|
||||
["ko"] = "이 플레이어와 채팅할 수 없어요.",
|
||||
["zh-tw"] = "您無法與此玩家聊天。",
|
||||
["zh-cn"] = "你无法与此玩家聊天。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_PrivateMessaging_CannotWhisperToSelf",
|
||||
Values = {
|
||||
["ru"] = "Нельзя отправлять себе личные сообщения.",
|
||||
["fr"] = "Vous ne pouvez pas murmurer à votre propre oreille.",
|
||||
["en-us"] = "You cannot whisper to yourself.",
|
||||
["de"] = "Du kannst dir nicht selbst etwas zuflüstern.",
|
||||
["it"] = "Non puoi aprire un canale privato con te stesso.",
|
||||
["pt"] = "Você não pode sussurrar para si mesmo.",
|
||||
["ja"] = "自分自身に話しかけることは出来ません。",
|
||||
["es"] = "No puedes enviarte mensajes privados a ti mismo.",
|
||||
["pt-br"] = "Você não pode sussurrar para si mesmo.",
|
||||
["ko"] = "자신에게 귓속말할 수 없어요.",
|
||||
["zh-tw"] = "您無法對自己耳語。",
|
||||
["zh-cn"] = "你无法向自己秘密发送消息。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_PrivateMessaging_NowChattingWith",
|
||||
Values = {
|
||||
["ru"] = "Открыт канал для личного общения с пользователем {RBX_NAME}",
|
||||
["fr"] = "Maintenant, vous discutez en privé avec {RBX_NAME}",
|
||||
["en-us"] = "You are now privately chatting with {RBX_NAME}",
|
||||
["de"] = "Du unterhältst dich nun privat mit {RBX_NAME}.",
|
||||
["it"] = "Ora stai chattando in privato con {RBX_NAME}",
|
||||
["pt"] = "Você agora está em um chat privado com {RBX_NAME}",
|
||||
["ja"] = "あなたは現在{RBX_NAME}とプライベートチャット中です。",
|
||||
["es"] = "Estás chateando en privado con {RBX_NAME}.",
|
||||
["pt-br"] = "Você agora está em um chat privado com {RBX_NAME}",
|
||||
["ko"] = "현재 {RBX_NAME} 님과 비공개 채팅 중이에요",
|
||||
["zh-tw"] = "您正在與{RBX_NAME}私聊",
|
||||
["zh-cn"] = "你正在与“{RBX_NAME}”私聊",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_TeamChat_WelcomeMessage",
|
||||
Values = {
|
||||
["ru"] = "Это канал для личного общения участников вашей команды.",
|
||||
["fr"] = "Ceci est un canal privé entre les membres de votre équipe et vous.",
|
||||
["en-us"] = "This is a private channel between you and your team members.",
|
||||
["de"] = "Dies ist ein privater Kanal für dich und deine Teammitglieder.",
|
||||
["it"] = "Questo è un canale privato tra te e i membri della tua squadra.",
|
||||
["pt"] = "Este é um canal privado entre você e os membros da sua equipe.",
|
||||
["ja"] = "これはあなたとあなたのチームメンバーとのプライベートチャンネルです。",
|
||||
["es"] = "Este es un canal privado entre tú y los miembros de tu equipo.",
|
||||
["pt-br"] = "Este é um canal privado entre você e os membros da sua equipe.",
|
||||
["ko"] = "회원님과 팀원 간의 비공개 채널이에요.",
|
||||
["zh-tw"] = "這是您與隊伍成員之間的私人頻道。",
|
||||
["zh-cn"] = "这是你与团队成员之间的私人频道。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_TeamChat_CannotTeamChatIfNotInTeam",
|
||||
Values = {
|
||||
["ru"] = "Вы не можете общаться в командном чате, если не состоите в команде!",
|
||||
["fr"] = "Vous ne pouvez pas avoir de discussion d'équipe si vous n'appartenez pas à une équipe !",
|
||||
["en-us"] = "You cannot team chat if you are not on a team!",
|
||||
["de"] = "Teamchat ist nur verfügbar, wenn du Mitglied eines Teams bist!",
|
||||
["it"] = "Non puoi chattare con la squadra se non sei in una squadra!",
|
||||
["pt"] = "Você não pode participar de chat de equipe se não estiver em uma!",
|
||||
["ja"] = "チームに所属していなければチームチャットは出来ません。",
|
||||
["es"] = "¡No puedes chatear con tu equipo si no formas parte de un equipo!",
|
||||
["pt-br"] = "Você não pode participar de chat de equipe se não estiver em uma!",
|
||||
["ko"] = "팀에 속하지 않으면 팀 채팅을 이용할 수 없어요!",
|
||||
["zh-tw"] = "若您不屬於隊伍,無法群聊!",
|
||||
["zh-cn"] = "如果你不在该团队,则无法进行团队聊天。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_TeamChat_NowInTeam",
|
||||
Values = {
|
||||
["ru"] = "Вы вступили в команду {RBX_NAME}.",
|
||||
["fr"] = "Vous êtes désormais dans l'équipe {RBX_NAME}.",
|
||||
["en-us"] = "You are now on the '{RBX_NAME}' team.",
|
||||
["de"] = "Du bist nun Mitglied im Team „{RBX_NAME}“.",
|
||||
["it"] = "Ora sei nella squadra \"{RBX_NAME}\".",
|
||||
["pt"] = "Você agora está na equipe '{RBX_NAME}'.",
|
||||
["ja"] = "あなたは現在 '{RBX_NAME}' チームに所属中です。",
|
||||
["es"] = "Ahora formas parte del equipo \"{RBX_NAME}\".",
|
||||
["pt-br"] = "Você agora está na equipe '{RBX_NAME}'.",
|
||||
["ko"] = "현재 '{RBX_NAME}'팀에 속해 있어요.",
|
||||
["zh-tw"] = "您目前在「{RBX_NAME}」隊。",
|
||||
["zh-cn"] = "你正在团队“{RBX_NAME}”中。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatChannel_MutedInChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы добавлены в список игнорируемых и не можете общаться на этом канале",
|
||||
["fr"] = "Vous êtes bâillonné et ne pouvez pas parler sur ce canal",
|
||||
["en-us"] = "You are muted and cannot talk in this channel",
|
||||
["de"] = "Du wurdest stummgeschaltet und kannst in diesem Kanal nicht kommunizieren.",
|
||||
["it"] = "Non hai più la parola e non puoi chattare in questo canale",
|
||||
["pt"] = "Você está silenciado(a) e não pode falar neste canal",
|
||||
["ja"] = "あなたはミュートされこのチャンネルで話すことは出来ません。",
|
||||
["es"] = "Se te ha silenciado y no puedes hablar en este canal.",
|
||||
["pt-br"] = "Você está silenciado(a) e não pode falar neste canal",
|
||||
["ko"] = "이 채널에서 음소거되어 이야기할 수 없어요",
|
||||
["zh-tw"] = "您遭靜音,無法在此頻道聊天",
|
||||
["zh-cn"] = "你已被静音,无法在此频道聊天",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatService_ChatFilterIssues",
|
||||
Values = {
|
||||
["ru"] = "Могут возникать задержки в передаче сообщений из-за проблем с фильтром чата.",
|
||||
["fr"] = "Le filtre de chat connaît actuellement des problèmes et les messages pourraient mettre du temps à apparaître.",
|
||||
["en-us"] = "The chat filter is currently experiencing issues and messages may be slow to appear.",
|
||||
["de"] = "Es gibt derzeit Probleme mit dem Chatfilter. Nachrichten können deshalb mit Verzögerung angezeigt werden.",
|
||||
["it"] = "Il filtro della chat sta riscontrando dei problemi e i messaggi potrebbero apparire in ritardo.",
|
||||
["pt"] = "O filtro de chat está com problemas no momento e as mensagens podem demorar para aparecer.",
|
||||
["ja"] = "現在チャットフィルターに問題があるためメッセージの表示が遅れています。",
|
||||
["es"] = "El filtro del chat sufre problemas en este momento y es posible que los mensajes tarden un poco en aparecer.",
|
||||
["pt-br"] = "O filtro de chat está com problemas no momento e as mensagens podem demorar para aparecer.",
|
||||
["ko"] = "현재 채팅 필터에 문제가 있어 메시지 표시가 느릴 수 있어요.",
|
||||
["zh-tw"] = "此聊天篩選條件目前遇到問題,訊息可能較慢顯示。",
|
||||
["zh-cn"] = "聊天过滤器当前遇到问题,消息显示可能出现延迟。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatService_YouHaveLeftChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы покинули канал «{RBX_NAME}»",
|
||||
["fr"] = "Vous avez quitté le canal {RBX_NAME}",
|
||||
["en-us"] = "You have left channel '{RBX_NAME}'",
|
||||
["de"] = "Du hast Kanal „{RBX_NAME}“ verlassen.",
|
||||
["it"] = "Hai lasciato il canale \"{RBX_NAME}\"",
|
||||
["pt"] = "Você saiu do canal '{RBX_NAME}'",
|
||||
["ja"] = "チャンネル '{RBX_NAME}' を退出しました。",
|
||||
["es"] = "Has salido del canal \"{RBX_NAME}\".",
|
||||
["pt-br"] = "Você saiu do canal '{RBX_NAME}'",
|
||||
["ko"] = "{RBX_NAME}' 채널에서 나왔어요",
|
||||
["zh-tw"] = "您已離開頻道「{RBX_NAME}」",
|
||||
["zh-cn"] = "你已离开频道“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatService_CannotLeaveChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы не можете покинуть этот канал.",
|
||||
["fr"] = "Vous ne pouvez pas quitter ce canal.",
|
||||
["en-us"] = "You cannot leave this channel.",
|
||||
["de"] = "Du kannst diesen Kanal nicht verlassen.",
|
||||
["it"] = "Non puoi lasciare questo canale.",
|
||||
["pt"] = "Você não pode sair deste canal.",
|
||||
["ja"] = "このチャンネルを退出することは出来ません。",
|
||||
["es"] = "No puedes salir de este canal.",
|
||||
["pt-br"] = "Você não pode sair deste canal.",
|
||||
["ko"] = "이 채널에서 나갈 수 없어요.",
|
||||
["zh-tw"] = "您無法離開此頻道。",
|
||||
["zh-cn"] = "你无法离开此频道。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatServiceRunner_YouCannotJoinChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы не можете подключиться к каналу «{RBX_NAME}»",
|
||||
["fr"] = "Vous ne pouvez pas rejoindre le canal {RBX_NAME}",
|
||||
["en-us"] = "You cannot join channel {RBX_NAME}",
|
||||
["de"] = "Du kannst Kanal „{RBX_NAME}“ nicht beitreten.",
|
||||
["it"] = "Non puoi accedere al canale {RBX_NAME}",
|
||||
["pt"] = "Você não pode entrar no canal {RBX_NAME}",
|
||||
["ja"] = "チャンネル {RBX_NAME} に参加することは出来ません。",
|
||||
["es"] = "No puedes unirte al canal {RBX_NAME}.",
|
||||
["pt-br"] = "Você não pode entrar no canal {RBX_NAME}",
|
||||
["ko"] = "{RBX_NAME} 채널에 가입할 수 없어요",
|
||||
["zh-tw"] = "您無法加入頻道{RBX_NAME}",
|
||||
["zh-cn"] = "你无法加入频道“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatServiceRunner_ChannelDoesNotExist",
|
||||
Values = {
|
||||
["ru"] = "Канала «{RBX_NAME}» не существует.",
|
||||
["fr"] = "Le canal {RBX_NAME} n'existe pas.",
|
||||
["en-us"] = "Channel {RBX_NAME} does not exist.",
|
||||
["de"] = "Kanal „{RBX_NAME}“ existiert nicht.",
|
||||
["it"] = "Il canale {RBX_NAME} non esiste.",
|
||||
["pt"] = "O canal {RBX_NAME} não existe.",
|
||||
["ja"] = "チャンネル {RBX_NAME} は存在しません。",
|
||||
["es"] = "El canal {RBX_NAME} no existe.",
|
||||
["pt-br"] = "O canal {RBX_NAME} não existe.",
|
||||
["ko"] = "{RBX_NAME} 채널이 없어요.",
|
||||
["zh-tw"] = "頻道{RBX_NAME}不存在。",
|
||||
["zh-cn"] = "频道“{RBX_NAME}”不存在。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatServiceRunner_YouCannotLeaveChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы не можете покинуть канал «{RBX_NAME}».",
|
||||
["fr"] = "Vous ne pouvez pas quitter le canal {RBX_NAME}",
|
||||
["en-us"] = "You cannot leave channel {RBX_NAME}",
|
||||
["de"] = "Du kannst Kanal „{RBX_NAME}“ nicht verlassen.",
|
||||
["it"] = "Non puoi lasciare il canale {RBX_NAME}",
|
||||
["pt"] = "Você não pode sair do canal {RBX_NAME}",
|
||||
["ja"] = "チャンネル {RBX_NAME}を退出することは出来ません。",
|
||||
["es"] = "No puedes salir del canal {RBX_NAME}.",
|
||||
["pt-br"] = "Você não pode sair do canal {RBX_NAME}",
|
||||
["ko"] = "{RBX_NAME} 채널에서 나갈 수 없어요",
|
||||
["zh-tw"] = "您無法離開頻道{RBX_NAME}",
|
||||
["zh-cn"] = "你无法离开频道“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatServiceRunner_YouAreNotInChannel",
|
||||
Values = {
|
||||
["ru"] = "Вы не на канале «{RBX_NAME}»",
|
||||
["fr"] = "Vous n'êtes pas sur le canal {RBX_NAME}",
|
||||
["en-us"] = "You are not in channel {RBX_NAME}",
|
||||
["de"] = "Du befindest dich nicht in Kanal „{RBX_NAME}“.",
|
||||
["it"] = "Non ti trovi nel canale {RBX_NAME}",
|
||||
["pt"] = "Você não está no canal {RBX_NAME}",
|
||||
["ja"] = "あなたはチャンネル {RBX_NAME} にいません。",
|
||||
["es"] = "No estás en el canal {RBX_NAME}.",
|
||||
["pt-br"] = "Você não está no canal {RBX_NAME}",
|
||||
["ko"] = "{RBX_NAME} 채널에 있지 않아요",
|
||||
["zh-tw"] = "您未在頻道{RBX_NAME}",
|
||||
["zh-cn"] = "你不在频道“{RBX_NAME}”",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_ChatServiceRunner_SystemChannelWelcomeMessage",
|
||||
Values = {
|
||||
["ru"] = "Этот канал предназначен для системных и игровых уведомлений.",
|
||||
["fr"] = "Ce canal est réservé aux notifications système et de jeu.",
|
||||
["en-us"] = "This channel is for system and game notifications.",
|
||||
["de"] = "Dieser Kanal ist für System- und Spielbenachrichtigungen.",
|
||||
["it"] = "Questo canale è per le notifiche di gioco e del sistema.",
|
||||
["pt"] = "Este canal é destinado a notificações do sistema e jogo.",
|
||||
["ja"] = "このチャンネルはシステムとゲーム通知のためのものです。",
|
||||
["es"] = "Este canal es para notificaciones del sistema y del juego.",
|
||||
["pt-br"] = "Este canal é destinado a notificações do sistema e jogo.",
|
||||
["ko"] = "이 채널은 시스템 및 게임 알림용이에요.",
|
||||
["zh-tw"] = "此頻道是供系統及遊戲通知用。",
|
||||
["zh-cn"] = "此频道用于发送系统及游戏通知。",
|
||||
}
|
||||
},
|
||||
{
|
||||
Key = "GameChat_FriendChatNotifier_JoinMessage",
|
||||
Values = {
|
||||
["ru"] = "Ваш друг {RBX_NAME} присоединился к игре.",
|
||||
["fr"] = "Votre ami {RBX_NAME} a rejoint le jeu.",
|
||||
["en-us"] = "Your friend {RBX_NAME} has joined the game.",
|
||||
["de"] = "Dein Freund {RBX_NAME} ist dem Spiel beigetreten.",
|
||||
["it"] = "Il tuo amico {RBX_NAME} è entrato nel gioco.",
|
||||
["pt"] = "Seu amigo {RBX_NAME} juntou-se ao jogo.",
|
||||
["ja"] = "あなたの友人{RBX_NAME}がゲームに参加しました。",
|
||||
["es"] = "Tu amigo {RBX_NAME} se ha unido al juego.",
|
||||
["pt-br"] = "Seu amigo {RBX_NAME} juntou-se ao jogo.",
|
||||
["ko"] = "친구 {RBX_NAME} 님이 게임에 가입했어요.",
|
||||
["zh-tw"] = "您的朋友{RBX_NAME}已加入遊戲。",
|
||||
["zh-cn"] = "你的朋友“{RBX_NAME}”已加入游戏。",
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
-- // FileName: ChatCommandsTeller.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module that provides information on default chat commands to players.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = { Get = function(key,default) return default end } end
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function ShowJoinAndLeaveCommands()
|
||||
if ChatSettings.ShowJoinAndLeaveHelpText ~= nil then
|
||||
return ChatSettings.ShowJoinAndLeaveHelpText
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function ProcessCommandsFunction(fromSpeaker, message, channel)
|
||||
if (message:lower() == "/?" or message:lower() == "/help") then
|
||||
local speaker = ChatService:GetSpeaker(fromSpeaker)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_Desc","These are the basic chat commands."), channel)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_MeCommand","/me <text> : roleplaying command for doing actions."), channel)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_SwitchChannelCommand","/c <channel> : switch channel menu tabs."), channel)
|
||||
if ShowJoinAndLeaveCommands() then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_JoinChannelCommand","/join <channel> or /j <channel> : join channel."), channel)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_LeaveChannelCommand","/leave <channel> or /l <channel> : leave channel. (leaves current if none specified)"), channel)
|
||||
end
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_WhisperCommand","/whisper <speaker> or /w <speaker> : open private message channel with speaker."), channel)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_MuteCommand","/mute <speaker> : mute a speaker."), channel)
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_UnMuteCommand","/unmute <speaker> : unmute a speaker."), channel)
|
||||
|
||||
local player = speaker:GetPlayer()
|
||||
if player and player.Team then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_ChatCommandsTeller_TeamCommand","/team <message> or /t <message> : send a team chat to players on your team."), channel)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("chat_commands_inquiry", ProcessCommandsFunction, ChatConstants.StandardPriority)
|
||||
|
||||
if ChatSettings.GeneralChannelName then
|
||||
local allChannel = ChatService:GetChannel(ChatSettings.GeneralChannelName)
|
||||
if (allChannel) then
|
||||
allChannel.WelcomeMessage = ChatLocalization:Get("GameChat_ChatCommandsTeller_AllChannelWelcomeMessage","Chat '/?' or '/help' for a list of chat commands.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Run
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
-- // FileName: ChatFloodDetector.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module that limits the number of messages a speaker can send in a given period of time.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local doFloodCheckByChannel = true
|
||||
local informSpeakersOfWaitTimes = true
|
||||
local chatBotsBypassFloodCheck = true
|
||||
local numberMessagesAllowed = 7
|
||||
local decayTimePeriod = 15
|
||||
|
||||
local floodCheckTable = {}
|
||||
local whitelistedSpeakers = {}
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local function EnterTimeIntoLog(tbl)
|
||||
table.insert(tbl, tick() + decayTimePeriod)
|
||||
end
|
||||
|
||||
local function Run(ChatService)
|
||||
local function FloodDetectionProcessCommandsFunction(speakerName, message, channel)
|
||||
if (whitelistedSpeakers[speakerName]) then return false end
|
||||
|
||||
local speakerObj = ChatService:GetSpeaker(speakerName)
|
||||
if (not speakerObj) then return false end
|
||||
if (chatBotsBypassFloodCheck and not speakerObj:GetPlayer()) then return false end
|
||||
|
||||
if (not floodCheckTable[speakerName]) then
|
||||
floodCheckTable[speakerName] = {}
|
||||
end
|
||||
|
||||
local t = nil
|
||||
|
||||
if (doFloodCheckByChannel) then
|
||||
if (not floodCheckTable[speakerName][channel]) then
|
||||
floodCheckTable[speakerName][channel] = {}
|
||||
end
|
||||
|
||||
t = floodCheckTable[speakerName][channel]
|
||||
else
|
||||
t = floodCheckTable[speakerName]
|
||||
end
|
||||
|
||||
local now = tick()
|
||||
while (#t > 0 and t[1] < now) do
|
||||
table.remove(t, 1)
|
||||
end
|
||||
|
||||
if (#t < numberMessagesAllowed) then
|
||||
EnterTimeIntoLog(t)
|
||||
return false
|
||||
else
|
||||
|
||||
local timeDiff = math.ceil(t[1] - now)
|
||||
|
||||
if (informSpeakersOfWaitTimes) then
|
||||
local msg = string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatFloodDetector_MessageDisplaySeconds",
|
||||
string.format("You must wait %d %s before sending another message!", timeDiff, (timeDiff > 1) and "seconds" or "second")
|
||||
),
|
||||
"{RBX_NUMBER}",
|
||||
tostring(timeDiff)
|
||||
)
|
||||
speakerObj:SendSystemMessage(msg, channel)
|
||||
else
|
||||
speakerObj:SendSystemMessage(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatFloodDetector_Message",
|
||||
"You must wait before sending another message!"
|
||||
)
|
||||
,channel)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("flood_detection", FloodDetectionProcessCommandsFunction, ChatConstants.LowPriority)
|
||||
|
||||
ChatService.SpeakerRemoved:connect(function(speakerName)
|
||||
floodCheckTable[speakerName] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
return Run
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
-- // FileName: ChatMessageValidator.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Validate things such as no disallowed whitespace and chat message length on the server.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local RunService = game:GetService("RunService")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local DISALLOWED_WHITESPACE = {"\n", "\r", "\t", "\v", "\f"}
|
||||
|
||||
if ChatSettings.DisallowedWhiteSpace then
|
||||
DISALLOWED_WHITESPACE = ChatSettings.DisallowedWhiteSpace
|
||||
end
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function CanUserChat(playerObj)
|
||||
if RunService:IsStudio() then
|
||||
return true
|
||||
end
|
||||
local success, canChat = pcall(function()
|
||||
return Chat:CanUserChatAsync(playerObj.UserId)
|
||||
end)
|
||||
return success and canChat
|
||||
end
|
||||
|
||||
local function ValidateChatFunction(speakerName, message, channel)
|
||||
local speakerObj = ChatService:GetSpeaker(speakerName)
|
||||
local playerObj = speakerObj:GetPlayer()
|
||||
if not speakerObj then return false end
|
||||
if not playerObj then return false end
|
||||
|
||||
if not RunService:IsStudio() and playerObj.UserId < 1 then
|
||||
return true
|
||||
end
|
||||
|
||||
if not CanUserChat(playerObj) then
|
||||
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_SettingsError","Your chat settings prevent you from sending messages."), channel)
|
||||
return true
|
||||
end
|
||||
|
||||
if message:len() > ChatSettings.MaximumMessageLength + 1 then
|
||||
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_MaxLengthError","Your message exceeds the maximum message length."), channel)
|
||||
return true
|
||||
end
|
||||
|
||||
for i = 1, #DISALLOWED_WHITESPACE do
|
||||
if string.find(message, DISALLOWED_WHITESPACE[i]) then
|
||||
speakerObj:SendSystemMessage(ChatLocalization:Get("GameChat_ChatMessageValidator_WhitespaceError","Your message contains whitespace that is not allowed."), channel)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("message_validation", ValidateChatFunction, ChatSettings.LowPriority)
|
||||
end
|
||||
|
||||
return Run
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
-- // FileName: ExtraDataInitializer.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module that sets some basic ExtraData such as name color, and chat color.
|
||||
|
||||
local SpecialChatColors = {
|
||||
Groups = {
|
||||
{
|
||||
--- ROBLOX Interns group
|
||||
GroupId = 2868472,
|
||||
Rank = 100,
|
||||
ChatColor = Color3.new(175/255, 221/255, 1),
|
||||
},
|
||||
{
|
||||
--- ROBLOX Admins group
|
||||
GroupId = 1200769,
|
||||
ChatColor = Color3.new(1, 215/255, 0),
|
||||
},
|
||||
},
|
||||
Players = {
|
||||
{
|
||||
--- Left as an example
|
||||
-- UserId = 2231221,
|
||||
-- ChatColor = Color3.new(205/255, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local function MakeIsInGroup(groupId, requiredRank)
|
||||
assert(type(requiredRank) == "nil" or type(requiredRank) == "number", "requiredRank must be a number or nil")
|
||||
|
||||
return function(player)
|
||||
if player and player.UserId then
|
||||
local userId = player.UserId
|
||||
|
||||
local inGroup = false
|
||||
local success, err = pcall(function() -- Many things can error is the IsInGroup check
|
||||
if requiredRank then
|
||||
inGroup = player:GetRankInGroup(groupId) > requiredRank
|
||||
else
|
||||
inGroup = player:IsInGroup(groupId)
|
||||
end
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error checking in group: " ..err)
|
||||
end
|
||||
|
||||
return inGroup
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function ConstructIsInGroups()
|
||||
if SpecialChatColors.Groups then
|
||||
for _, group in pairs(SpecialChatColors.Groups) do
|
||||
group.IsInGroup = MakeIsInGroup(group.GroupId, group.Rank)
|
||||
end
|
||||
end
|
||||
end
|
||||
ConstructIsInGroups()
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local function GetSpecialChatColor(speakerName)
|
||||
if SpecialChatColors.Players then
|
||||
local playerFromSpeaker = Players:FindFirstChild(speakerName)
|
||||
if playerFromSpeaker then
|
||||
for _, player in pairs(SpecialChatColors.Players) do
|
||||
if playerFromSpeaker.UserId == player.UserId then
|
||||
return player.ChatColor
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if SpecialChatColors.Groups then
|
||||
for _, group in pairs(SpecialChatColors.Groups) do
|
||||
if group.IsInGroup(Players:FindFirstChild(speakerName)) then
|
||||
return group.ChatColor
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function Run(ChatService)
|
||||
local NAME_COLORS =
|
||||
{
|
||||
Color3.new(253/255, 41/255, 67/255), -- BrickColor.new("Bright red").Color,
|
||||
Color3.new(1/255, 162/255, 255/255), -- BrickColor.new("Bright blue").Color,
|
||||
Color3.new(2/255, 184/255, 87/255), -- BrickColor.new("Earth green").Color,
|
||||
BrickColor.new("Bright violet").Color,
|
||||
BrickColor.new("Bright orange").Color,
|
||||
BrickColor.new("Bright yellow").Color,
|
||||
BrickColor.new("Light reddish violet").Color,
|
||||
BrickColor.new("Brick yellow").Color,
|
||||
}
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local color_offset = 0
|
||||
local function ComputeNameColor(pName)
|
||||
return NAME_COLORS[((GetNameValue(pName) + color_offset) % #NAME_COLORS) + 1]
|
||||
end
|
||||
|
||||
local function GetNameColor(speaker)
|
||||
local player = speaker:GetPlayer()
|
||||
if player then
|
||||
if player.Team ~= nil then
|
||||
return player.TeamColor.Color
|
||||
end
|
||||
end
|
||||
return ComputeNameColor(speaker.Name)
|
||||
end
|
||||
|
||||
local function onNewSpeaker(speakerName)
|
||||
local speaker = ChatService:GetSpeaker(speakerName)
|
||||
if not speaker:GetExtraData("NameColor") then
|
||||
speaker:SetExtraData("NameColor", GetNameColor(speaker))
|
||||
end
|
||||
if not speaker:GetExtraData("ChatColor") then
|
||||
local specialChatColor = GetSpecialChatColor(speakerName)
|
||||
if specialChatColor then
|
||||
speaker:SetExtraData("ChatColor", specialChatColor)
|
||||
end
|
||||
end
|
||||
if not speaker:GetExtraData("Tags") then
|
||||
--// Example of how you would set tags
|
||||
--[[
|
||||
local tags = {
|
||||
{
|
||||
TagText = "VIP",
|
||||
TagColor = Color3.new(1, 215/255, 0)
|
||||
},
|
||||
{
|
||||
TagText = "Alpha Tester",
|
||||
TagColor = Color3.new(205/255, 0, 0)
|
||||
}
|
||||
}
|
||||
speaker:SetExtraData("Tags", tags)
|
||||
]]
|
||||
speaker:SetExtraData("Tags", {})
|
||||
end
|
||||
end
|
||||
|
||||
ChatService.SpeakerAdded:connect(onNewSpeaker)
|
||||
|
||||
for _, speakerName in pairs(ChatService:GetSpeakerList()) do
|
||||
onNewSpeaker(speakerName)
|
||||
end
|
||||
|
||||
local PlayerChangedConnections = {}
|
||||
Players.PlayerAdded:connect(function(player)
|
||||
local changedConn = player.Changed:connect(function(property)
|
||||
local speaker = ChatService:GetSpeaker(player.Name)
|
||||
if speaker then
|
||||
if property == "TeamColor" or property == "Neutral" or property == "Team" then
|
||||
speaker:SetExtraData("NameColor", GetNameColor(speaker))
|
||||
end
|
||||
end
|
||||
end)
|
||||
PlayerChangedConnections[player] = changedConn
|
||||
end)
|
||||
|
||||
Players.PlayerRemoving:connect(function(player)
|
||||
local changedConn = PlayerChangedConnections[player]
|
||||
if changedConn then
|
||||
changedConn:Disconnect()
|
||||
end
|
||||
PlayerChangedConnections[player] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
return Run
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
-- // FileName: FriendJoinNotifer.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Module that adds a message to the chat whenever a friend joins the game.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local Players = game:GetService("Players")
|
||||
local FriendService = game:GetService("FriendService")
|
||||
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local FriendMessageTextColor = Color3.fromRGB(255, 255, 255)
|
||||
local FriendMessageExtraData = {ChatColor = FriendMessageTextColor}
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function ShowFriendJoinNotification()
|
||||
if ChatSettings.ShowFriendJoinNotification ~= nil then
|
||||
return ChatSettings.ShowFriendJoinNotification
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function SendFriendJoinNotification(player, joinedFriend)
|
||||
local speakerObj = ChatService:GetSpeaker(player.Name)
|
||||
if speakerObj then
|
||||
speakerObj:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_FriendChatNotifier_JoinMessage",
|
||||
string.format("Your friend %s has joined the game.", joinedFriend.Name)
|
||||
),
|
||||
"{RBX_NAME}",
|
||||
joinedFriend.Name
|
||||
),
|
||||
"System",
|
||||
FriendMessageExtraData
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function TrySendFriendNotification(player, joinedPlayer)
|
||||
if player ~= joinedPlayer then
|
||||
coroutine.wrap(function()
|
||||
if player:IsFriendsWith(joinedPlayer.UserId) then
|
||||
SendFriendJoinNotification(player, joinedPlayer)
|
||||
end
|
||||
end)()
|
||||
end
|
||||
end
|
||||
|
||||
if ShowFriendJoinNotification() then
|
||||
Players.PlayerAdded:connect(function(player)
|
||||
local possibleFriends = Players:GetPlayers()
|
||||
for i = 1, #possibleFriends do
|
||||
TrySendFriendNotification(possibleFriends[i], player)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
return Run
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
-- // FileName: MeCommand.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Sets the type of /me messages.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function MeCommandFilterFunction(speakerName, messageObj, channelName)
|
||||
local message = messageObj.Message
|
||||
if message and string.sub(message, 1, 4):lower() == "/me " then
|
||||
-- Set a different message type so that clients can render the message differently.
|
||||
messageObj.MessageType = ChatConstants.MessageTypeMeCommand
|
||||
end
|
||||
end
|
||||
|
||||
ChatService:RegisterFilterMessageFunction("me_command", MeCommandFilterFunction)
|
||||
end
|
||||
|
||||
return Run
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
-- // FileName: MuteSpeaker.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Module that handles all the mute and unmute commands.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
|
||||
local errorExtraData = {ChatColor = errorTextColor}
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function GetSpeakerNameFromMessage(message)
|
||||
local speakerName = message
|
||||
if string.sub(message, 1, 1) == "\"" then
|
||||
local pos = string.find(message, "\"", 2)
|
||||
if pos then
|
||||
speakerName = string.sub(message, 2, pos - 1)
|
||||
end
|
||||
else
|
||||
local first = string.match(message, "^[^%s]+")
|
||||
if first then
|
||||
speakerName = first
|
||||
end
|
||||
end
|
||||
return speakerName
|
||||
end
|
||||
|
||||
local function DoMuteCommand(speakerName, message, channel)
|
||||
local muteSpeakerName = GetSpeakerNameFromMessage(message)
|
||||
local speaker = ChatService:GetSpeaker(speakerName)
|
||||
if speaker then
|
||||
if muteSpeakerName:lower() == speakerName:lower() then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_DoMuteCommand_CannotMuteSelf","You cannot mute yourself."), channel, errorExtraData)
|
||||
return
|
||||
end
|
||||
|
||||
local muteSpeaker = ChatService:GetSpeaker(muteSpeakerName)
|
||||
if muteSpeaker then
|
||||
speaker:AddMutedSpeaker(muteSpeaker.Name)
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatMain_SpeakerHasBeenMuted",
|
||||
string.format("Speaker '%s' has been muted.", muteSpeaker.Name)
|
||||
),
|
||||
"{RBX_NAME}",muteSpeaker.Name
|
||||
),
|
||||
channel
|
||||
)
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
|
||||
string.format("Speaker '%s' does not exist.", tostring(muteSpeakerName))
|
||||
),
|
||||
"{RBX_NAME}",tostring(muteSpeakerName)
|
||||
),
|
||||
channel,
|
||||
errorExtraData
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function DoUnmuteCommand(speakerName, message, channel)
|
||||
local unmuteSpeakerName = GetSpeakerNameFromMessage(message)
|
||||
local speaker = ChatService:GetSpeaker(speakerName)
|
||||
if speaker then
|
||||
if unmuteSpeakerName:lower() == speakerName:lower() then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_DoMuteCommand_CannotMuteSelf","You cannot mute yourself."), channel, errorExtraData)
|
||||
return
|
||||
end
|
||||
|
||||
local unmuteSpeaker = ChatService:GetSpeaker(unmuteSpeakerName)
|
||||
if unmuteSpeaker then
|
||||
speaker:RemoveMutedSpeaker(unmuteSpeaker.Name)
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_ChatMain_SpeakerHasBeenUnMuted",
|
||||
string.format("Speaker '%s' has been unmuted.", unmuteSpeaker.Name)
|
||||
),
|
||||
"{RBX_NAME}",unmuteSpeaker.Name
|
||||
),
|
||||
channel
|
||||
)
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
|
||||
string.format("Speaker '%s' does not exist.", tostring(unmuteSpeakerName))
|
||||
),
|
||||
"{RBX_NAME}",tostring(unmuteSpeakerName)
|
||||
),
|
||||
channel,
|
||||
errorExtraData
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MuteCommandsFunction(fromSpeaker, message, channel)
|
||||
local processedCommand = false
|
||||
|
||||
if string.sub(message, 1, 6):lower() == "/mute " then
|
||||
DoMuteCommand(fromSpeaker, string.sub(message, 7), channel)
|
||||
processedCommand = true
|
||||
elseif string.sub(message, 1, 8):lower() == "/unmute " then
|
||||
DoUnmuteCommand(fromSpeaker, string.sub(message, 9), channel)
|
||||
processedCommand = true
|
||||
end
|
||||
return processedCommand
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("mute_commands", MuteCommandsFunction, ChatConstants.StandardPriority)
|
||||
end
|
||||
|
||||
return Run
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
-- // FileName: PrivateMessaging.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module that handles all private messaging.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local RunService = game:GetService("RunService")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
|
||||
local errorExtraData = {ChatColor = errorTextColor}
|
||||
|
||||
function GetWhisperChannelPrefix()
|
||||
if ChatConstants.WhisperChannelPrefix then
|
||||
return ChatConstants.WhisperChannelPrefix
|
||||
end
|
||||
return "To "
|
||||
end
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local function CanCommunicate(fromSpeaker, toSpeaker)
|
||||
if RunService:IsStudio() then
|
||||
return true
|
||||
end
|
||||
local fromPlayer = fromSpeaker:GetPlayer()
|
||||
local toPlayer = toSpeaker:GetPlayer()
|
||||
if fromPlayer and toPlayer then
|
||||
local success, canChat = pcall(function()
|
||||
return Chat:CanUsersChatAsync(fromPlayer.UserId, toPlayer.UserId)
|
||||
end)
|
||||
return success and canChat
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function DoWhisperCommand(fromSpeaker, message, channel)
|
||||
local otherSpeakerName = message
|
||||
local sendMessage = nil
|
||||
|
||||
if (string.sub(message, 1, 1) == "\"") then
|
||||
local pos = string.find(message, "\"", 2)
|
||||
if (pos) then
|
||||
otherSpeakerName = string.sub(message, 2, pos - 1)
|
||||
sendMessage = string.sub(message, pos + 2)
|
||||
end
|
||||
else
|
||||
local first = string.match(message, "^[^%s]+")
|
||||
if (first) then
|
||||
otherSpeakerName = first
|
||||
sendMessage = string.sub(message, string.len(otherSpeakerName) + 2)
|
||||
end
|
||||
end
|
||||
|
||||
local speaker = ChatService:GetSpeaker(fromSpeaker)
|
||||
local otherSpeaker = ChatService:GetSpeaker(otherSpeakerName)
|
||||
local channelObj = ChatService:GetChannel(GetWhisperChannelPrefix() .. otherSpeakerName)
|
||||
if channelObj and otherSpeaker then
|
||||
if not CanCommunicate(speaker, otherSpeaker) then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_PrivateMessaging_CannotChat","You are not able to chat with this player."), channel, errorExtraData)
|
||||
return
|
||||
end
|
||||
|
||||
if (channelObj.Name == GetWhisperChannelPrefix() .. speaker.Name) then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_PrivateMessaging_CannotWhisperToSelf","You cannot whisper to yourself."), channel, errorExtraData)
|
||||
else
|
||||
if (not speaker:IsInChannel(channelObj.Name)) then
|
||||
speaker:JoinChannel(channelObj.Name)
|
||||
end
|
||||
|
||||
if (sendMessage and (string.len(sendMessage) > 0) ) then
|
||||
speaker:SayMessage(sendMessage, channelObj.Name)
|
||||
end
|
||||
|
||||
speaker:SetMainChannel(channelObj.Name)
|
||||
|
||||
end
|
||||
|
||||
else
|
||||
speaker:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_MuteSpeaker_SpeakerDoesNotExist",
|
||||
string.format("Speaker '%s' does not exist.", tostring(otherSpeakerName))
|
||||
),
|
||||
"{RBX_NAME}",tostring(otherSpeakerName)
|
||||
),
|
||||
channel,
|
||||
errorExtraData
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local function WhisperCommandsFunction(fromSpeaker, message, channel)
|
||||
local processedCommand = false
|
||||
|
||||
if (string.sub(message, 1, 3):lower() == "/w ") then
|
||||
DoWhisperCommand(fromSpeaker, string.sub(message, 4), channel)
|
||||
processedCommand = true
|
||||
|
||||
elseif (string.sub(message, 1, 9):lower() == "/whisper ") then
|
||||
DoWhisperCommand(fromSpeaker, string.sub(message, 10), channel)
|
||||
processedCommand = true
|
||||
|
||||
end
|
||||
|
||||
return processedCommand
|
||||
end
|
||||
|
||||
local function PrivateMessageReplicationFunction(fromSpeaker, message, channelName)
|
||||
local sendingSpeaker = ChatService:GetSpeaker(fromSpeaker)
|
||||
local extraData = sendingSpeaker.ExtraData
|
||||
sendingSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
|
||||
|
||||
local toSpeaker = ChatService:GetSpeaker(string.sub(channelName, 4))
|
||||
if (toSpeaker) then
|
||||
if (not toSpeaker:IsInChannel(GetWhisperChannelPrefix() .. fromSpeaker)) then
|
||||
toSpeaker:JoinChannel(GetWhisperChannelPrefix() .. fromSpeaker)
|
||||
end
|
||||
toSpeaker:SendMessage(message, GetWhisperChannelPrefix() .. fromSpeaker, fromSpeaker, extraData)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function PrivateMessageAddTypeFunction(speakerName, messageObj, channelName)
|
||||
if ChatConstants.MessageTypeWhisper then
|
||||
messageObj.MessageType = ChatConstants.MessageTypeWhisper
|
||||
end
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("whisper_commands", WhisperCommandsFunction, ChatConstants.StandardPriority)
|
||||
|
||||
local function GetWhisperChanneNameColor()
|
||||
if ChatSettings.WhisperChannelNameColor then
|
||||
return ChatSettings.WhisperChannelNameColor
|
||||
end
|
||||
return Color3.fromRGB(102, 14, 102)
|
||||
end
|
||||
|
||||
ChatService.SpeakerAdded:connect(function(speakerName)
|
||||
if (ChatService:GetChannel(GetWhisperChannelPrefix() .. speakerName)) then
|
||||
ChatService:RemoveChannel(GetWhisperChannelPrefix() .. speakerName)
|
||||
end
|
||||
|
||||
local channel = ChatService:AddChannel(GetWhisperChannelPrefix() .. speakerName)
|
||||
channel.Joinable = false
|
||||
channel.Leavable = true
|
||||
channel.AutoJoin = false
|
||||
channel.Private = true
|
||||
|
||||
channel.WelcomeMessage = string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_PrivateMessaging_NowChattingWith",
|
||||
"You are now privately chatting with " .. speakerName .. "."
|
||||
),
|
||||
"{RBX_NAME}",tostring(speakerName)
|
||||
)
|
||||
channel.ChannelNameColor = GetWhisperChanneNameColor()
|
||||
|
||||
channel:RegisterProcessCommandsFunction("replication_function", PrivateMessageReplicationFunction, ChatConstants.LowPriority)
|
||||
channel:RegisterFilterMessageFunction("message_type_function", PrivateMessageAddTypeFunction)
|
||||
end)
|
||||
|
||||
ChatService.SpeakerRemoved:connect(function(speakerName)
|
||||
if (ChatService:GetChannel(GetWhisperChannelPrefix() .. speakerName)) then
|
||||
ChatService:RemoveChannel(GetWhisperChannelPrefix() .. speakerName)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return Run
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
-- // FileName: TeamChat.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: Module that handles all team chat.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatSettings = require(ReplicatedModules:WaitForChild("ChatSettings"))
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local ChatLocalization = nil
|
||||
pcall(function() ChatLocalization = require(game:GetService("Chat").ClientChatModules.ChatLocalization) end)
|
||||
if ChatLocalization == nil then ChatLocalization = {} function ChatLocalization:Get(key,default) return default end end
|
||||
|
||||
local errorTextColor = ChatSettings.ErrorMessageTextColor or Color3.fromRGB(245, 50, 50)
|
||||
local errorExtraData = {ChatColor = errorTextColor}
|
||||
|
||||
local function Run(ChatService)
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local channel = ChatService:AddChannel("Team")
|
||||
channel.WelcomeMessage = ChatLocalization:Get("GameChat_TeamChat_WelcomeMessage","This is a private channel between you and your team members.")
|
||||
channel.Joinable = false
|
||||
channel.Leavable = false
|
||||
channel.AutoJoin = false
|
||||
channel.Private = true
|
||||
|
||||
local function TeamChatReplicationFunction(fromSpeaker, message, channelName)
|
||||
local speakerObj = ChatService:GetSpeaker(fromSpeaker)
|
||||
local channelObj = ChatService:GetChannel(channelName)
|
||||
if (speakerObj and channelObj) then
|
||||
local player = speakerObj:GetPlayer()
|
||||
if (player) then
|
||||
|
||||
for i, speakerName in pairs(channelObj:GetSpeakerList()) do
|
||||
local otherSpeaker = ChatService:GetSpeaker(speakerName)
|
||||
if (otherSpeaker) then
|
||||
local otherPlayer = otherSpeaker:GetPlayer()
|
||||
if (otherPlayer) then
|
||||
|
||||
if (player.Team == otherPlayer.Team) then
|
||||
local extraData = {
|
||||
NameColor = player.TeamColor.Color,
|
||||
ChatColor = player.TeamColor.Color,
|
||||
ChannelColor = player.TeamColor.Color
|
||||
}
|
||||
otherSpeaker:SendMessage(message, channelName, fromSpeaker, extraData)
|
||||
else
|
||||
--// Could use this line to obfuscate message for cool effects
|
||||
--otherSpeaker:SendMessage(message, channelName, fromSpeaker)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
channel:RegisterProcessCommandsFunction("replication_function", TeamChatReplicationFunction, ChatConstants.LowPriority)
|
||||
|
||||
local function DoTeamCommand(fromSpeaker, message, channel)
|
||||
if message == nil then
|
||||
message = ""
|
||||
end
|
||||
|
||||
local speaker = ChatService:GetSpeaker(fromSpeaker)
|
||||
if speaker then
|
||||
local player = speaker:GetPlayer()
|
||||
|
||||
if player then
|
||||
if player.Team == nil then
|
||||
speaker:SendSystemMessage(ChatLocalization:Get("GameChat_TeamChat_CannotTeamChatIfNotInTeam","You cannot team chat if you are not on a team!"), channel, errorExtraData)
|
||||
return
|
||||
end
|
||||
|
||||
local channelObj = ChatService:GetChannel("Team")
|
||||
if channelObj then
|
||||
if not speaker:IsInChannel(channelObj.Name) then
|
||||
speaker:JoinChannel(channelObj.Name)
|
||||
end
|
||||
if message and string.len(message) > 0 then
|
||||
speaker:SayMessage(message, channelObj.Name)
|
||||
end
|
||||
speaker:SetMainChannel(channelObj.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function TeamCommandsFunction(fromSpeaker, message, channel)
|
||||
local processedCommand = false
|
||||
|
||||
if message == nil then
|
||||
error("Message is nil")
|
||||
end
|
||||
|
||||
if channel == "Team" then
|
||||
return false
|
||||
end
|
||||
|
||||
if string.sub(message, 1, 6):lower() == "/team " or message:lower() == "/team" then
|
||||
DoTeamCommand(fromSpeaker, string.sub(message, 7), channel)
|
||||
processedCommand = true
|
||||
elseif string.sub(message, 1, 3):lower() == "/t " or message:lower() == "/t" then
|
||||
DoTeamCommand(fromSpeaker, string.sub(message, 4), channel)
|
||||
processedCommand = true
|
||||
elseif string.sub(message, 1, 2):lower() == "% " or message:lower() == "%" then
|
||||
DoTeamCommand(fromSpeaker, string.sub(message, 3), channel)
|
||||
processedCommand = true
|
||||
end
|
||||
|
||||
return processedCommand
|
||||
end
|
||||
|
||||
ChatService:RegisterProcessCommandsFunction("team_commands", TeamCommandsFunction, ChatConstants.StandardPriority)
|
||||
|
||||
local function GetDefaultChannelNameColor()
|
||||
if ChatSettings.DefaultChannelNameColor then
|
||||
return ChatSettings.DefaultChannelNameColor
|
||||
end
|
||||
return Color3.fromRGB(35, 76, 142)
|
||||
end
|
||||
|
||||
local function PutSpeakerInCorrectTeamChatState(speakerObj, playerObj)
|
||||
if playerObj.Neutral or playerObj.Team == nil then
|
||||
speakerObj:UpdateChannelNameColor(channel.Name, GetDefaultChannelNameColor())
|
||||
|
||||
if speakerObj:IsInChannel(channel.Name) then
|
||||
speakerObj:LeaveChannel(channel.Name)
|
||||
end
|
||||
elseif not playerObj.Neutral and playerObj.Team then
|
||||
speakerObj:UpdateChannelNameColor(channel.Name, playerObj.Team.TeamColor.Color)
|
||||
|
||||
if not speakerObj:IsInChannel(channel.Name) then
|
||||
speakerObj:JoinChannel(channel.Name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatService.SpeakerAdded:connect(function(speakerName)
|
||||
local speakerObj = ChatService:GetSpeaker(speakerName)
|
||||
if speakerObj then
|
||||
local player = speakerObj:GetPlayer()
|
||||
if player then
|
||||
PutSpeakerInCorrectTeamChatState(speakerObj, player)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local PlayerChangedConnections = {}
|
||||
Players.PlayerAdded:connect(function(player)
|
||||
local changedConn = player.Changed:connect(function(property)
|
||||
local speakerObj = ChatService:GetSpeaker(player.Name)
|
||||
if speakerObj then
|
||||
if property == "Neutral" then
|
||||
PutSpeakerInCorrectTeamChatState(speakerObj, player)
|
||||
elseif property == "Team" then
|
||||
PutSpeakerInCorrectTeamChatState(speakerObj, player)
|
||||
if speakerObj:IsInChannel(channel.Name) then
|
||||
speakerObj:SendSystemMessage(
|
||||
string.gsub(
|
||||
ChatLocalization:Get(
|
||||
"GameChat_TeamChat_NowInTeam",
|
||||
string.format("You are now on the '%s' team.", player.Team.Name)
|
||||
),
|
||||
"{RBX_NAME}",player.Team.Name
|
||||
),
|
||||
channel.Name
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
PlayerChangedConnections[player] = changedConn
|
||||
end)
|
||||
|
||||
Players.PlayerRemoving:connect(function(player)
|
||||
local changedConn = PlayerChangedConnections[player]
|
||||
if changedConn then
|
||||
changedConn:Disconnect()
|
||||
end
|
||||
PlayerChangedConnections[player] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
return Run
|
||||
@@ -0,0 +1,134 @@
|
||||
## Documentation
|
||||
### ChatService
|
||||
This is the main singleton object that runs the server sided chat service. It manages both ChatChannels and Speakers.
|
||||
|
||||
#### Methods
|
||||
ChatChannel AddChannel(string channelName, bool autoJoin)
|
||||
void RemoveChannel(string channelName)
|
||||
ChatChannel GetChannel(string channelName)
|
||||
|
||||
Speaker AddSpeaker(string speakerName)
|
||||
void RemoveSpeaker(string speakerName)
|
||||
Speaker GetSpeaker(string speakerName)
|
||||
|
||||
string[] GetChannelList()
|
||||
string[] GetAutoJoinChannelList()
|
||||
|
||||
void RegisterFilterMessageFunction(string functionId, function func, int priority)
|
||||
bool FilterMessageFunctionExists(string functionId)
|
||||
void UnregisterFilterMessageFunction(string functionId)
|
||||
|
||||
void RegisterProcessCommandsFunction(string functionId, function func, int priority)
|
||||
bool ProcessCommandsFunctionExists(string functionId)
|
||||
void UnregisterProcessCommandsFunction(string functionId)
|
||||
|
||||
|
||||
#### Events
|
||||
ChannelAdded(string channelName)
|
||||
ChannelRemoved(string channelName)
|
||||
SpeakerAdded(string speakerName)
|
||||
SpeakerRemoved(string speakerName)
|
||||
|
||||
|
||||
### ChatChannel
|
||||
A ChatChannel is an object that stores data about a single channel that Speakers can chat in. The ChatChannel manages a list of Speakers currently in the channel for relaying messages between them, and also comes with some access permission properties.
|
||||
|
||||
#### Properties
|
||||
string Name (read-only)
|
||||
string WelcomeMessage
|
||||
|
||||
bool Joinable
|
||||
bool Leavable
|
||||
bool AutoJoin
|
||||
bool Private
|
||||
|
||||
#### Methods
|
||||
void RegisterFilterMessageFunction(string functionId, function func, int priority)
|
||||
bool FilterMessageFunctionExists(string functionId)
|
||||
void UnregisterFilterMessageFunction(string functionId)
|
||||
|
||||
void RegisterProcessCommandsFunction(string functionId, function func, int priority)
|
||||
bool ProcessCommandsFunctionExists(string functionId)
|
||||
void UnregisterProcessCommandsFunction(string functionId)
|
||||
|
||||
void KickSpeaker(string speakerName, string reason)
|
||||
void MuteSpeaker(string speakerName, string reason, int length)
|
||||
void UnmuteSpeaker(string speakerName)
|
||||
bool IsSpeakerMuted(string speakerName)
|
||||
|
||||
string[] GetSpeakerList()
|
||||
|
||||
void SendSystemMessage(string message)
|
||||
|
||||
MessageObject[] GetHistoryLogForSpeaker(Speaker speaker)
|
||||
|
||||
void RegisterGetWelcomeMessageFunction(function func)
|
||||
void UnRegisterGetWelcomeMessageFunction()
|
||||
string GetWelcomeMessageForSpeaker(Speaker speaker)
|
||||
|
||||
#### Events
|
||||
MessagePosted(table messageObj)
|
||||
SpeakerJoined(string speakerName)
|
||||
SpeakerLeft(string speakerName)
|
||||
SpeakerMuted(string speakerName, string reason, int length)
|
||||
SpeakerUnmuted(string speakerName)
|
||||
|
||||
### Speaker
|
||||
A Speaker object is a representation of one entity that can speak in a ChatChannel. A Speaker can be a Player, or it can be a chat bot that is run and managed by code.
|
||||
|
||||
#### Properties
|
||||
string Name (read-only)
|
||||
|
||||
#### Methods
|
||||
void SayMessage(string message, string channelName)
|
||||
|
||||
void JoinChannel(string channelName)
|
||||
void LeaveChannel(string channelName)
|
||||
|
||||
string[] GetChannelList()
|
||||
bool IsInChannel(string channelName)
|
||||
|
||||
void SendMessage(string fromSpeaker, string channel, string message)
|
||||
void SendSystemMessage(string message, string channel)
|
||||
|
||||
Player GetPlayer() (returns nil for non-player speakers)
|
||||
|
||||
void SetExtraData(string key, Variant data)
|
||||
Variant GetExtraData(string key)
|
||||
|
||||
#### Events
|
||||
SaidMessage(table messageObject, string channelName)
|
||||
ReceivedMessage(table messageObject, string channelName)
|
||||
ReceivedSystemMessage(table messageObject, string channelName)
|
||||
ChannelJoined(string channelName, string channelWelcomeMessage)
|
||||
ChannelLeft(string channelName)
|
||||
Muted(string channelName, string reason, int length)
|
||||
Unmuted(string channelName)
|
||||
ExtraDataUpdated(string key, Variant value)
|
||||
MainChannelSet(string channelName)
|
||||
|
||||
___
|
||||
|
||||
#### Message Object format
|
||||
```
|
||||
{
|
||||
int ID
|
||||
string FromSpeaker
|
||||
int SpeakerUserId
|
||||
string OriginalChannel
|
||||
bool IsFiltered
|
||||
int MessageLength
|
||||
string Message
|
||||
string MessageType
|
||||
int Time
|
||||
table ExtraData {
|
||||
Color3 ChatColor
|
||||
Color3 NameColor
|
||||
Enum.Font Font
|
||||
int TextSize
|
||||
table Tags
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Message will not exist on the client if IsFiltered is False
|
||||
SpeakerUserId will be 0 if the speaker is not a player.
|
||||
@@ -0,0 +1,330 @@
|
||||
-- // FileName: Speaker.lua
|
||||
-- // Written by: Xsitsu
|
||||
-- // Description: A representation of one entity that can chat in different ChatChannels.
|
||||
|
||||
local module = {}
|
||||
|
||||
local modulesFolder = script.Parent
|
||||
|
||||
--////////////////////////////// Methods
|
||||
--//////////////////////////////////////
|
||||
local function ShallowCopy(table)
|
||||
local copy = {}
|
||||
for i, v in pairs(table) do
|
||||
copy[i] = v
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
local methods = {}
|
||||
|
||||
local lazyEventNames =
|
||||
{
|
||||
eDestroyed = true,
|
||||
eSaidMessage = true,
|
||||
eReceivedMessage = true,
|
||||
eReceivedUnfilteredMessage = true,
|
||||
eMessageDoneFiltering = true,
|
||||
eReceivedSystemMessage = true,
|
||||
eChannelJoined = true,
|
||||
eChannelLeft = true,
|
||||
eMuted = true,
|
||||
eUnmuted = true,
|
||||
eExtraDataUpdated = true,
|
||||
eMainChannelSet = true,
|
||||
eChannelNameColorUpdated = true,
|
||||
}
|
||||
local lazySignalNames =
|
||||
{
|
||||
Destroyed = "eDestroyed",
|
||||
SaidMessage = "eSaidMessage",
|
||||
ReceivedMessage = "eReceivedMessage",
|
||||
ReceivedUnfilteredMessage = "eReceivedUnfilteredMessage",
|
||||
RecievedUnfilteredMessage = "eReceivedUnfilteredMessage", -- legacy mispelling
|
||||
MessageDoneFiltering = "eMessageDoneFiltering",
|
||||
ReceivedSystemMessage = "eReceivedSystemMessage",
|
||||
ChannelJoined = "eChannelJoined",
|
||||
ChannelLeft = "eChannelLeft",
|
||||
Muted = "eMuted",
|
||||
Unmuted = "eUnmuted",
|
||||
ExtraDataUpdated = "eExtraDataUpdated",
|
||||
MainChannelSet = "eMainChannelSet",
|
||||
ChannelNameColorUpdated = "eChannelNameColorUpdated"
|
||||
}
|
||||
|
||||
methods.__index = function (self, k)
|
||||
local fromMethods = rawget(methods, k)
|
||||
if fromMethods then return fromMethods end
|
||||
|
||||
if lazyEventNames[k] and not rawget(self, k) then
|
||||
rawset(self, k, Instance.new("BindableEvent"))
|
||||
end
|
||||
local lazySignalEventName = lazySignalNames[k]
|
||||
if lazySignalEventName and not rawget(self, k) then
|
||||
if not rawget(self, lazySignalEventName) then
|
||||
rawset(self, lazySignalEventName, Instance.new("BindableEvent"))
|
||||
end
|
||||
rawset(self, k, rawget(self, lazySignalEventName).Event)
|
||||
end
|
||||
return rawget(self, k)
|
||||
end
|
||||
|
||||
function methods:LazyFire(eventName, ...)
|
||||
local createdEvent = rawget(self, eventName)
|
||||
if createdEvent then
|
||||
createdEvent:Fire(...)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SayMessage(message, channelName, extraData)
|
||||
if (self.ChatService:InternalDoProcessCommands(self.Name, message, channelName)) then return end
|
||||
if (not channelName) then return end
|
||||
|
||||
local channel = self.Channels[channelName:lower()]
|
||||
if (not channel) then
|
||||
error("Speaker is not in channel \"" .. channelName .. "\"")
|
||||
end
|
||||
|
||||
local messageObj = channel:InternalPostMessage(self, message, extraData)
|
||||
if (messageObj) then
|
||||
local success, err = pcall(function() self:LazyFire("eSaidMessage", messageObj, channelName) end)
|
||||
if not success and err then
|
||||
print("Error saying message: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
return messageObj
|
||||
end
|
||||
|
||||
function methods:JoinChannel(channelName)
|
||||
if (self.Channels[channelName:lower()]) then
|
||||
warn("Speaker is already in channel \"" .. channelName .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local channel = self.ChatService:GetChannel(channelName)
|
||||
if (not channel) then
|
||||
error("Channel \"" .. channelName .. "\" does not exist!")
|
||||
end
|
||||
|
||||
self.Channels[channelName:lower()] = channel
|
||||
channel:InternalAddSpeaker(self)
|
||||
local success, err = pcall(function()
|
||||
self.eChannelJoined:Fire(channel.Name, channel:GetWelcomeMessageForSpeaker(self))
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error joining channel: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:LeaveChannel(channelName)
|
||||
if (not self.Channels[channelName:lower()]) then
|
||||
warn("Speaker is not in channel \"" .. channelName .. "\"")
|
||||
return
|
||||
end
|
||||
|
||||
local channel = self.Channels[channelName:lower()]
|
||||
|
||||
self.Channels[channelName:lower()] = nil
|
||||
channel:InternalRemoveSpeaker(self)
|
||||
local success, err = pcall(function()
|
||||
self:LazyFire("eChannelLeft", channel.Name)
|
||||
self.EventFolder.OnChannelLeft:FireClient(self.PlayerObj, channel.Name)
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error leaving channel: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:IsInChannel(channelName)
|
||||
return (self.Channels[channelName:lower()] ~= nil)
|
||||
end
|
||||
|
||||
function methods:GetChannelList()
|
||||
local list = {}
|
||||
for i, channel in pairs(self.Channels) do
|
||||
table.insert(list, channel.Name)
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
function methods:SendMessage(message, channelName, fromSpeaker, extraData)
|
||||
local channel = self.Channels[channelName:lower()]
|
||||
if (channel) then
|
||||
channel:SendMessageToSpeaker(message, self.Name, fromSpeaker, extraData)
|
||||
|
||||
else
|
||||
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a message in it.", self.Name, channelName))
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function methods:SendSystemMessage(message, channelName, extraData)
|
||||
local channel = self.Channels[channelName:lower()]
|
||||
if (channel) then
|
||||
channel:SendSystemMessageToSpeaker(message, self.Name, extraData)
|
||||
|
||||
else
|
||||
warn(string.format("Speaker '%s' is not in channel '%s' and cannot receive a system message in it.", self.Name, channelName))
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
function methods:GetPlayer()
|
||||
return self.PlayerObj
|
||||
end
|
||||
|
||||
function methods:SetExtraData(key, value)
|
||||
self.ExtraData[key] = value
|
||||
self:LazyFire("eExtraDataUpdated", key, value)
|
||||
end
|
||||
|
||||
function methods:GetExtraData(key)
|
||||
return self.ExtraData[key]
|
||||
end
|
||||
|
||||
function methods:SetMainChannel(channelName)
|
||||
local success, err = pcall(function()
|
||||
self:LazyFire("eMainChannelSet", channelName)
|
||||
self.EventFolder.OnMainChannelSet:FireClient(self.PlayerObj, channelName)
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error setting main channel: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
--- Used to mute a speaker so that this speaker does not see their messages.
|
||||
function methods:AddMutedSpeaker(speakerName)
|
||||
self.MutedSpeakers[speakerName:lower()] = true
|
||||
end
|
||||
|
||||
function methods:RemoveMutedSpeaker(speakerName)
|
||||
self.MutedSpeakers[speakerName:lower()] = false
|
||||
end
|
||||
|
||||
function methods:IsSpeakerMuted(speakerName)
|
||||
return self.MutedSpeakers[speakerName:lower()]
|
||||
end
|
||||
|
||||
--///////////////// Internal-Use Methods
|
||||
--//////////////////////////////////////
|
||||
function methods:InternalDestroy()
|
||||
for i, channel in pairs(self.Channels) do
|
||||
channel:InternalRemoveSpeaker(self)
|
||||
end
|
||||
|
||||
self.eDestroyed:Fire()
|
||||
|
||||
self.EventFolder = nil
|
||||
self.eDestroyed:Destroy()
|
||||
self.eSaidMessage:Destroy()
|
||||
self.eReceivedMessage:Destroy()
|
||||
self.eReceivedUnfilteredMessage:Destroy()
|
||||
self.eMessageDoneFiltering:Destroy()
|
||||
self.eReceivedSystemMessage:Destroy()
|
||||
self.eChannelJoined:Destroy()
|
||||
self.eChannelLeft:Destroy()
|
||||
self.eMuted:Destroy()
|
||||
self.eUnmuted:Destroy()
|
||||
self.eExtraDataUpdated:Destroy()
|
||||
self.eMainChannelSet:Destroy()
|
||||
self.eChannelNameColorUpdated:Destroy()
|
||||
end
|
||||
|
||||
function methods:InternalAssignPlayerObject(playerObj)
|
||||
self.PlayerObj = playerObj
|
||||
end
|
||||
|
||||
function methods:InternalAssignEventFolder(eventFolder)
|
||||
self.EventFolder = eventFolder
|
||||
end
|
||||
|
||||
function methods:InternalSendMessage(messageObj, channelName)
|
||||
local success, err = pcall(function()
|
||||
self:LazyFire("eReceivedUnfilteredMessage", messageObj, channelName)
|
||||
self.EventFolder.OnNewMessage:FireClient(self.PlayerObj, messageObj, channelName)
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error sending internal message: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalSendFilteredMessage(messageObj, channelName)
|
||||
local success, err = pcall(function()
|
||||
self:LazyFire("eReceivedMessage", messageObj, channelName)
|
||||
self:LazyFire("eMessageDoneFiltering", messageObj, channelName)
|
||||
self.EventFolder.OnMessageDoneFiltering:FireClient(self.PlayerObj, messageObj, channelName)
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error sending internal filtered message: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
--// This method is to be used with the new filter API. This method takes the
|
||||
--// TextFilterResult objects and converts them into the appropriate string
|
||||
--// messages for each player.
|
||||
function methods:InternalSendFilteredMessageWithFilterResult(inMessageObj, channelName)
|
||||
local messageObj = ShallowCopy(inMessageObj)
|
||||
|
||||
local oldFilterResult = messageObj.FilterResult
|
||||
local player = self:GetPlayer()
|
||||
|
||||
local msg = ""
|
||||
pcall(function()
|
||||
if (messageObj.IsFilterResult) then
|
||||
if (player) then
|
||||
msg = oldFilterResult:GetChatForUserAsync(player.UserId)
|
||||
else
|
||||
msg = oldFilterResult:GetNonChatStringForBroadcastAsync()
|
||||
end
|
||||
else
|
||||
msg = oldFilterResult
|
||||
end
|
||||
end)
|
||||
|
||||
--// Messages of 0 length are the result of two users not being allowed
|
||||
--// to chat, or GetChatForUserAsync() failing. In both of these situations,
|
||||
--// messages with length of 0 should not be sent.
|
||||
if (#msg > 0) then
|
||||
messageObj.Message = msg
|
||||
messageObj.FilterResult = nil
|
||||
self:InternalSendFilteredMessage(messageObj, channelName)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:InternalSendSystemMessage(messageObj, channelName)
|
||||
local success, err = pcall(function()
|
||||
self:LazyFire("eReceivedSystemMessage", messageObj, channelName)
|
||||
self.EventFolder.OnNewSystemMessage:FireClient(self.PlayerObj, messageObj, channelName)
|
||||
end)
|
||||
if not success and err then
|
||||
print("Error sending internal system message: " ..err)
|
||||
end
|
||||
end
|
||||
|
||||
function methods:UpdateChannelNameColor(channelName, channelNameColor)
|
||||
self:LazyFire("eChannelNameColorUpdated", channelName, channelNameColor)
|
||||
self.EventFolder.ChannelNameColorUpdated:FireClient(self.PlayerObj, channelName, channelNameColor)
|
||||
end
|
||||
|
||||
--///////////////////////// Constructors
|
||||
--//////////////////////////////////////
|
||||
|
||||
function module.new(vChatService, name)
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.ChatService = vChatService
|
||||
|
||||
obj.PlayerObj = nil
|
||||
|
||||
obj.Name = name
|
||||
obj.ExtraData = {}
|
||||
|
||||
obj.Channels = {}
|
||||
obj.MutedSpeakers = {}
|
||||
obj.EventFolder = nil
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return module
|
||||
@@ -0,0 +1,110 @@
|
||||
-- // FileName: Util.lua
|
||||
-- // Written by: TheGamer101
|
||||
-- // Description: Utility code used by the server side chat implementation.
|
||||
|
||||
local Chat = game:GetService("Chat")
|
||||
local ReplicatedModules = Chat:WaitForChild("ClientChatModules")
|
||||
local ChatConstants = require(ReplicatedModules:WaitForChild("ChatConstants"))
|
||||
|
||||
local DEFAULT_PRIORITY = ChatConstants.StandardPriority
|
||||
if DEFAULT_PRIORITY == nil then
|
||||
DEFAULT_PRIORITY = 10
|
||||
end
|
||||
|
||||
local Util = {}
|
||||
Util.__index = Util
|
||||
|
||||
local SortedFunctionContainer = {}; do
|
||||
-- This sorted function container is used to handle the logic around storing filter functions and
|
||||
-- command processors by priority.
|
||||
|
||||
local methods = {}
|
||||
methods.__index = methods
|
||||
|
||||
function methods:RebuildProcessCommandsPriorities()
|
||||
self.RegisteredPriorites = {}
|
||||
for priority, functions in pairs(self.FunctionMap) do
|
||||
local functionsEmpty = true
|
||||
for funcId, funciton in pairs(functions) do
|
||||
functionsEmpty = false
|
||||
break
|
||||
end
|
||||
if not functionsEmpty then
|
||||
table.insert(self.RegisteredPriorites, priority)
|
||||
end
|
||||
end
|
||||
table.sort(self.RegisteredPriorites, function(a, b)
|
||||
return a > b
|
||||
end)
|
||||
end
|
||||
|
||||
function methods:HasFunction(funcId)
|
||||
if self.RegisteredFunctions[funcId] == nil then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function methods:RemoveFunction(funcId)
|
||||
local functionPriority = self.RegisteredFunctions[funcId]
|
||||
self.RegisteredFunctions[funcId] = nil
|
||||
self.FunctionMap[functionPriority][funcId] = nil
|
||||
self:RebuildProcessCommandsPriorities()
|
||||
end
|
||||
|
||||
function methods:AddFunction(funcId, func, priority)
|
||||
if priority == nil then
|
||||
priority = DEFAULT_PRIORITY
|
||||
end
|
||||
|
||||
if self.RegisteredFunctions[funcId] then
|
||||
error(funcId .. " is already in use!")
|
||||
end
|
||||
|
||||
self.RegisteredFunctions[funcId] = priority
|
||||
|
||||
if self.FunctionMap[priority] == nil then
|
||||
self.FunctionMap[priority] = {}
|
||||
end
|
||||
|
||||
self.FunctionMap[priority][funcId] = func
|
||||
self:RebuildProcessCommandsPriorities()
|
||||
end
|
||||
|
||||
function methods:GetIterator()
|
||||
local priorityIndex = 1
|
||||
local funcId = nil
|
||||
local func = nil
|
||||
|
||||
return function()
|
||||
while true do
|
||||
if priorityIndex > #self.RegisteredPriorites then
|
||||
return
|
||||
end
|
||||
local priority = self.RegisteredPriorites[priorityIndex]
|
||||
funcId, func = next(self.FunctionMap[priority], funcId)
|
||||
if funcId == nil then
|
||||
priorityIndex = priorityIndex + 1
|
||||
else
|
||||
return funcId, func, priority
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SortedFunctionContainer.new()
|
||||
local obj = setmetatable({}, methods)
|
||||
|
||||
obj.RegisteredFunctions = {}
|
||||
obj.RegisteredPriorites = {}
|
||||
obj.FunctionMap = {}
|
||||
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
function Util:NewSortedFunctionContainer()
|
||||
return SortedFunctionContainer.new()
|
||||
end
|
||||
|
||||
return Util
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
-- If you want to create your own custom player settings,
|
||||
-- make a copy of this script and add it to your StarterPlayer object
|
||||
|
||||
local module = {}
|
||||
|
||||
module.UseDefaultAnimations = false -- force incoming players to use default avatar animations, not custom animations
|
||||
|
||||
return module
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
|
||||
local Install = function ()
|
||||
|
||||
local playerConfig = game:GetService("StarterPlayer"):FindFirstChild("PlayerSettings")
|
||||
if (playerConfig == nil) then
|
||||
playerConfig = Instance.new("ModuleScript")
|
||||
playerConfig.Name = "PlayerSettings"
|
||||
playerConfig.Source = script.Parent.DefaultServerPlayerModules:WaitForChild("PlayerSettings").Source
|
||||
playerConfig.Parent = game:GetService("StarterPlayer")
|
||||
playerConfig.Archivable = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
return Install
|
||||
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
--[[
|
||||
The sound dispatcher will fire sound events to properly loaded characters. This script manages a list of
|
||||
characters currently loaded in the game. When a character fires a sound event, this dispatcher will
|
||||
check to make sure the event only fires on characters who have loaded in.
|
||||
--]]
|
||||
|
||||
local Players = game:GetService("Players")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local SOUND_EVENT_FOLDER_NAME = "DefaultSoundEvents"
|
||||
|
||||
if UserSettings():IsUserFeatureEnabled("UserUseSoundDispatcher") then
|
||||
local loadedCharacters = {}
|
||||
|
||||
local EventFolder = ReplicatedStorage:FindFirstChild(SOUND_EVENT_FOLDER_NAME)
|
||||
if not EventFolder then
|
||||
EventFolder = Instance.new("Folder")
|
||||
EventFolder.Name = SOUND_EVENT_FOLDER_NAME
|
||||
EventFolder.Archivable = false
|
||||
EventFolder.Parent = ReplicatedStorage
|
||||
end
|
||||
|
||||
local function createEvent(name, instanceType)
|
||||
local newEvent = EventFolder:FindFirstChild(name)
|
||||
if not newEvent then
|
||||
newEvent = Instance.new(instanceType)
|
||||
newEvent.Name = name
|
||||
newEvent.Parent = EventFolder
|
||||
end
|
||||
|
||||
return newEvent
|
||||
end
|
||||
|
||||
local DefaultServerSoundEvent = createEvent("DefaultServerSoundEvent", "RemoteEvent")
|
||||
local AddCharacterLoadedEvent = createEvent("AddCharacterLoadedEvent", "RemoteEvent")
|
||||
local RemoveCharacterEvent = createEvent("RemoveCharacterEvent", "RemoteEvent")
|
||||
|
||||
-- Fire the sound event to all clients connected
|
||||
local function fireDefaultServerSoundEventToClient(player, sound, playing, resetPosition)
|
||||
if loadedCharacters[player] then
|
||||
DefaultServerSoundEvent:FireClient(player, sound, playing, resetPosition)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add a character to the list of clients ready to receive sounds
|
||||
local function addCharacterLoaded(player)
|
||||
loadedCharacters[player] = true
|
||||
end
|
||||
|
||||
-- Remove a character from the table
|
||||
local function removeCharacter(player)
|
||||
loadedCharacters[player] = nil
|
||||
end
|
||||
|
||||
local soundDispatcher = createEvent("SoundDispatcher", "BindableEvent")
|
||||
soundDispatcher.Event:Connect(fireDefaultServerSoundEventToClient)
|
||||
|
||||
--no op function to prevent rogue client from filling RemoteEvent queue
|
||||
DefaultServerSoundEvent.OnServerEvent:Connect(function() end)
|
||||
AddCharacterLoadedEvent.OnServerEvent:Connect(addCharacterLoaded)
|
||||
RemoveCharacterEvent.OnServerEvent:Connect(removeCharacter)
|
||||
Players.PlayerRemoving:Connect(removeCharacter)
|
||||
end
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
-- Load the SoundDispatcher script into Studio
|
||||
local soundDispatcherName = "SoundDispatcher"
|
||||
local ServerScriptService = game:GetService("ServerScriptService")
|
||||
|
||||
local function LoadScript(name, parent)
|
||||
local originalModule = script.Parent:WaitForChild(name)
|
||||
local script = Instance.new("Script")
|
||||
script.Name = name
|
||||
script.Source = originalModule.Source
|
||||
script.Parent = parent
|
||||
return script
|
||||
end
|
||||
|
||||
local function Install()
|
||||
local soundDispatcherArchivable = true
|
||||
local SoundDispatcher = ServerScriptService:FindFirstChild(soundDispatcherName)
|
||||
if not SoundDispatcher then
|
||||
soundDispatcherArchivable = false
|
||||
SoundDispatcher = LoadScript(soundDispatcherName, ServerScriptService)
|
||||
end
|
||||
|
||||
if not ServerScriptService:FindFirstChild(soundDispatcherName) then
|
||||
local SoundDispatcherCopy = SoundDispatcher:Clone()
|
||||
SoundDispatcherCopy.Archivable = false
|
||||
SoundDispatcherCopy.Parent = ServerScriptService
|
||||
end
|
||||
|
||||
SoundDispatcher.Archivable = soundDispatcherArchivable
|
||||
end
|
||||
|
||||
return Install
|
||||
@@ -0,0 +1 @@
|
||||
return {}
|
||||
Reference in New Issue
Block a user