This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,29 @@
stds.roblox = {
globals = {
"game",
"script",
},
read_globals = {
-- Extra functions
"tick", "warn", "spawn",
"wait", "settings", "typeof",
"delay", "time", "version",
-- Libraries
"debug",
-- Types
"Enum",
"Axes", "BrickColor", "CFrame", "Color3", "ColorSequence",
"ColorSequenceKeypoint", "Faces","Instance","NumberRange",
"NumberSequence", "NumberSequenceKeypoint", "PhysicalProperties",
"Ray", "Rect", "Region3", "Region3int16", "TweenInfo",
"UDim", "UDim2",
"Vector2", "Vector2int16", "Vector3", "Vector3int16",
}
}
ignore = {
"212", -- unused arguments
}
std = "lua51+roblox"
@@ -0,0 +1,282 @@
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local TeleportService = game:GetService("TeleportService")
local AnalyticsService = game:GetService("AnalyticsService")
local create = require(RobloxGui.Modules.Common.Create)
local ErrorPrompt = require(RobloxGui.Modules.ErrorPrompt)
local LEAVE_GAME_FRAME_WAITS = 2
local function safeGetFInt(name, defaultValue)
local success, result = pcall(function() return tonumber(settings():GetFVariable(name)) end)
return success and result or defaultValue
end
local inGameGlobalGuiInset = safeGetFInt("InGameGlobalGuiInset", 36)
local defaultTimeoutTime = safeGetFInt("DefaultTimeoutTimeMs", 10000) / 1000
local errorPrompt
local graceTimeout = -1
local screenWidth = RobloxGui.AbsoluteSize.X
local ConnectionPromptState = {
NONE = 1, -- General Error Message
RECONNECT_PLACELAUNCH = 2, -- Show PlaceLaunching Reconnect Options
RECONNECT_DISCONNECT = 3, -- Show Disconnect Reconnect Options
TELEPORT_FAILED = 4, -- Show Teleport Failure Message
IS_RECONNECTING = 5, -- Show Reconnecting Animation
RECONNECT_DISABLED = 6, -- i.e After player being kicked from server
}
local connectionPromptState = ConnectionPromptState.NONE
-- error that triggers reconnection
local errorForReconnect = Enum.ConnectionError.OK
-- this will be loaded from localization table
local ErrorTitles = {
[ConnectionPromptState.RECONNECT_PLACELAUNCH] = "Join Error",
[ConnectionPromptState.RECONNECT_DISCONNECT] = "Disconnected",
[ConnectionPromptState.RECONNECT_DISABLED] = "Disconnected",
[ConnectionPromptState.TELEPORT_FAILED] = "Teleport Failed",
}
-- might have different design on xbox
-- if GuiService:IsTenFootInterface() then
-- errorPrompt = ErrorPrompt.new("XBox")
errorPrompt = ErrorPrompt.new("Default")
-- Screengui holding the prompt and make it on top of blur
local screenGui = create 'ScreenGui' {
Parent = CoreGui,
Name = "RobloxPromptGui",
OnTopOfCoreBlur = true
}
-- semi-transparent frame overlay
local promptOverlay = create 'Frame' {
Name = 'promptOverlay',
BackgroundColor3 = Color3.new(0, 0, 0),
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, inGameGlobalGuiInset),
Position = UDim2.new(0, 0, 0, -inGameGlobalGuiInset),
Active = false,
Parent = screenGui
}
errorPrompt:setParent(promptOverlay)
-- Button Callbacks --
local reconnectFunction = function()
if connectionPromptState == ConnectionPromptState.IS_RECONNECTING then
return
end
AnalyticsService:ReportCounter("ReconnectPrompt-ReconnectActivated")
connectionPromptState = ConnectionPromptState.IS_RECONNECTING
errorPrompt:primaryShimmerPlay()
-- Wait until it passes the defaultTimeOut
local currentTime = tick()
if currentTime < graceTimeout then
wait(graceTimeout - currentTime)
end
TeleportService:Teleport(game.placeId)
end
local leaveFunction = function()
GuiService.SelectedCoreObject = nil
for i = 1, LEAVE_GAME_FRAME_WAITS do
RunService.RenderStepped:wait()
end
game:Shutdown()
end
local closePrompt = function()
GuiService:ClearError()
end
-- Button Callbacks --
-- Reconnect Disabled List
local reconnectDisabledList = {
[Enum.ConnectionError.DisconnectLuaKick] = true,
[Enum.ConnectionError.DisconnectSecurityKeyMismatch] = true,
[Enum.ConnectionError.DisconnectNewSecurityKeyMismatch] = true,
[Enum.ConnectionError.DisconnectDuplicateTicket] = true,
[Enum.ConnectionError.DisconnectWrongVersion] = true,
[Enum.ConnectionError.DisconnectProtocolMismatch] = true,
[Enum.ConnectionError.DisconnectBadhash] = true,
[Enum.ConnectionError.DisconnectIllegalTeleport] = true,
[Enum.ConnectionError.DisconnectDuplicatePlayer] = true,
[Enum.ConnectionError.DisconnectPlayerless] = true,
[Enum.ConnectionError.DisconnectCloudEditKick] = true,
[Enum.ConnectionError.DisconnectHashTimeout] = true,
[Enum.ConnectionError.DisconnectOnRemoteSysStats] = true,
}
local ButtonList = {
[ConnectionPromptState.RECONNECT_PLACELAUNCH] = {
{
Text = "Retry",
LayoutOrder = 2,
Callback = reconnectFunction,
Primary = true
},
{
Text = "Cancel",
LayoutOrder = 1,
Callback = leaveFunction,
}
},
[ConnectionPromptState.RECONNECT_DISCONNECT] = {
{
Text = "Reconnect",
LayoutOrder = 2,
Callback = reconnectFunction,
Primary = true
},
{
Text = "Leave",
LayoutOrder = 1,
Callback = leaveFunction,
}
},
[ConnectionPromptState.TELEPORT_FAILED] = {
{
Text = "OK",
LayoutOrder = 1,
Callback = closePrompt,
Primary = true,
}
},
[ConnectionPromptState.RECONNECT_DISABLED] = {
{
Text = "Leave",
LayoutOrder = 1,
Callback = leaveFunction,
Primary = true,
}
}
}
local updateFullScreenEffect = {
[ConnectionPromptState.NONE] = function()
RunService:SetRobloxGuiFocused(false)
promptOverlay.Active = false
promptOverlay.Transparency = 1
end,
[ConnectionPromptState.RECONNECT_DISCONNECT] = function()
RunService:SetRobloxGuiFocused(true)
promptOverlay.Active = true
promptOverlay.Transparency = 1
end,
[ConnectionPromptState.RECONNECT_PLACELAUNCH] = function()
RunService:SetRobloxGuiFocused(false)
promptOverlay.Active = true
promptOverlay.Transparency = 0.3
end,
[ConnectionPromptState.TELEPORT_FAILED] = function()
RunService:SetRobloxGuiFocused(false)
promptOverlay.Active = true
promptOverlay.Transparency = 0.3
end,
[ConnectionPromptState.RECONNECT_DISABLED] = function()
RunService:SetRobloxGuiFocused(true)
promptOverlay.Active = true
promptOverlay.Transparency = 1
end,
}
local function onEnter(newState)
if updateFullScreenEffect[newState] then
updateFullScreenEffect[newState]()
end
errorPrompt:setErrorTitle(ErrorTitles[newState])
errorPrompt:updateButtons(ButtonList[newState])
end
local function onExit(oldState)
if oldState == ConnectionPromptState.IS_RECONNECTING then
errorPrompt:primaryShimmerStop()
end
end
-- state transit function
local function stateTransit(errorType, errorCode, oldState)
if errorType == Enum.ConnectionError.OK then
return ConnectionPromptState.NONE
end
if reconnectDisabledList[errorCode] then
return ConnectionPromptState.RECONNECT_DISABLED
end
if oldState == ConnectionPromptState.NONE then
if errorType == Enum.ConnectionError.DisconnectErrors then
-- reconnection will be delayed after graceTimeout
graceTimeout = tick() + defaultTimeoutTime
errorForReconnect = Enum.ConnectionError.DisconnectErrors
return ConnectionPromptState.RECONNECT_DISCONNECT
elseif errorType == Enum.ConnectionError.PlacelaunchErrors then
errorForReconnect = Enum.ConnectionError.PlacelaunchErrors
return ConnectionPromptState.RECONNECT_PLACELAUNCH
elseif errorType == Enum.ConnectionError.TeleportErrors then
return ConnectionPromptState.TELEPORT_FAILED
end
end
if oldState == ConnectionPromptState.IS_RECONNECTING then
-- if is reconnecting, then it is the reconnect failure
AnalyticsService:ReportCounter("ReconnectPrompt-ReconnectFailed")
if errorType == Enum.ConnectionError.TeleportErrors then
if errorForReconnect == Enum.ConnectionError.PlacelaunchErrors then
return ConnectionPromptState.RECONNECT_PLACELAUNCH
elseif errorForReconnect == Enum.ConnectionError.DisconnectErrors then
return ConnectionPromptState.RECONNECT_DISCONNECT
end
end
end
return oldState
end
local function updateErrorPrompt(errorMsg, errorCode, errorType)
local newPromptState = stateTransit(errorType, errorCode, connectionPromptState)
if newPromptState ~= connectionPromptState then
onExit(connectionPromptState)
connectionPromptState = newPromptState
onEnter(newPromptState)
end
if connectionPromptState ~= ConnectionPromptState.TELEPORT_FAILED then
errorMsg = string.match(errorMsg, "Teleport Failed: (.*)") or errorMsg
end
errorPrompt:onErrorChanged(errorMsg, errorCode)
end
local function onErrorMessageChanged()
local errorMsg = GuiService:GetErrorMessage()
local errorCode = GuiService:GetErrorCode()
local errorType = GuiService:GetErrorType()
updateErrorPrompt(errorMsg, errorCode, errorType)
end
local function onScreenSizeChanged()
local newWidth = RobloxGui.AbsoluteSize.X
if screenWidth ~= newWidth then
screenWidth = newWidth
errorPrompt:resizeWidth(screenWidth)
end
end
-- adjust size to the current screen width
errorPrompt:resizeWidth(screenWidth)
RobloxGui:GetPropertyChangedSignal("AbsoluteSize"):connect(onScreenSizeChanged)
-- pre-run it once in case some error occurs before the connection
onErrorMessageChanged()
GuiService.ErrorMessageChanged:connect(onErrorMessageChanged)
@@ -0,0 +1,330 @@
--[[
// FileName: AvatarContextMenu.lua
// Written by: TheGamer101
// Description: A context menu to allow users to click on avatars and then interact with that user.
]]
-- OPTIONS
local DEBUG_MODE = game:GetService("RunService"):IsStudio() -- use this to run as a guest/use in games that don't have AvatarContextMenu. FOR TESTING ONLY!
local isAvatarContextMenuEnabled = false
-- CONSTANTS
local MAX_CONTEXT_MENU_DISTANCE = 100
local OPEN_MENU_TIME = 0.2
local OPEN_MENU_TWEEN = TweenInfo.new(OPEN_MENU_TIME, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local CLOSE_MENU_TIME = 0.2
local CLOSE_MENU_TWEEN = TweenInfo.new(CLOSE_MENU_TIME, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local LEAVE_MENU_ACTION_NAME = "EscapeAvatarContextMenu"
local MAX_MOVEMENT_THRESHOLD = 20
-- SERVICES
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local PlayersService = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local CoreGuiService = game:GetService("CoreGui")
local StarterGui = game:GetService("StarterGui")
local GuiService = game:GetService("GuiService")
local AnalyticsService = game:GetService("AnalyticsService")
--- SETCORE METHODS
-- These must be registered before we start requiring modules so they are available on the first frame.
-- This hack is ugly because it obscures the stack trace for these set core errors. We should one day just make the LocalPlayer
-- exist on the first frame.
local TempSetCoreQueue = {}
function QueueSetCoreMethod(methodName, args)
if TempSetCoreQueue[methodName] == nil then
TempSetCoreQueue[methodName] = {}
end
table.insert(TempSetCoreQueue[methodName], args)
end
StarterGui:RegisterSetCore("AddAvatarContextMenuOption", function(...) QueueSetCoreMethod("AddAvatarContextMenuOption", {...}) end)
StarterGui:RegisterSetCore("RemoveAvatarContextMenuOption", function(...) QueueSetCoreMethod("RemoveAvatarContextMenuOption", {...}) end)
local hasTrackedAvatarContextMenu = false
StarterGui:RegisterSetCore("SetAvatarContextMenuEnabled",
function(enabled) isAvatarContextMenuEnabled = not not enabled
if isAvatarContextMenuEnabled and not hasTrackedAvatarContextMenu then
hasTrackedAvatarContextMenu = true
AnalyticsService:TrackEvent("Game", "AvatarContextMenuEnabled", "placeId: " .. tostring(game.PlaceId))
end
end
)
--- MODULES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
local ContextMenuGui = require(AvatarMenuModules:WaitForChild("ContextMenuGui"))
local ContextMenuItemsModule = require(AvatarMenuModules:WaitForChild("ContextMenuItems"))
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
local SelectedCharacterIndicator = require(AvatarMenuModules:WaitForChild("SelectedCharacterIndicator"))
--- VARIABLES
local LocalPlayer = PlayersService.LocalPlayer
while not LocalPlayer do
PlayersService.PlayerAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
-- no avatar context menu for guests
if LocalPlayer.UserId <= 0 and not DEBUG_MODE then return end
local ContextMenuItems = nil
local ContextMenuFrame = nil
local ContextMenuOpening = false
local ContextMenuOpen = false
local SelectedPlayer = nil
local lastInputObject = nil
local initialScreenPoint = nil
local hasTouchSwipeInput = nil
local contextMenuPlayerChangedConn = nil
ContextMenuFrame = ContextMenuGui:CreateMenuFrame()
ContextMenuItems = ContextMenuItemsModule.new(ContextMenuFrame.Content.ContextActionList)
-- SetCores have been registered, empty SetCoreQueue
for setCoreMethod, queue in pairs(TempSetCoreQueue) do
for i = 1, #queue do
StarterGui:SetCore(setCoreMethod, unpack(queue[i]))
end
end
function SetSelectedPlayer(player, dontTween)
if SelectedPlayer == player then return end
SelectedPlayer = player
SelectedCharacterIndicator:ChangeSelectedPlayer(SelectedPlayer)
ContextMenuItems:BuildContextMenuItems(SelectedPlayer)
ContextMenuGui:SwitchToPlayerEntry(SelectedPlayer, dontTween)
end
function OpenMenu()
ContextMenuOpening = true
ContextMenuFrame.Visible = true
ContextMenuFrame.Content.ContextActionList.CanvasPosition = Vector2.new(0,0)
ContextMenuFrame.Position = UDim2.new(0.5, 0, 1, ContextMenuFrame.AbsoluteSize.Y)
contextMenuPlayerChangedConn = ContextMenuGui.SelectedPlayerChanged:connect(function()
SetSelectedPlayer(ContextMenuGui:GetSelectedPlayer())
end)
local positionTween = TweenService:Create(ContextMenuFrame, OPEN_MENU_TWEEN, {Position = UDim2.new(0.5, 0, 1 - ContextMenuGui:GetBottomScreenPaddingConstant(), 0)})
positionTween:Play()
positionTween.Completed:wait()
ContextMenuOpening = false
end
function BindMenuActions()
-- Close Menu actions
local closeMenuFunc = function(actionName, inputState, input)
if inputState ~= Enum.UserInputState.Begin then
return
end
ContextActionService:UnbindCoreAction(LEAVE_MENU_ACTION_NAME)
CloseContextMenu()
end
ContextActionService:BindCoreAction(LEAVE_MENU_ACTION_NAME, closeMenuFunc, false, Enum.KeyCode.Escape)
local menuOpenedCon = nil
menuOpenedCon = GuiService.MenuOpened:connect(function()
menuOpenedCon:disconnect()
closeMenuFunc(nil, Enum.UserInputState.Begin, nil)
end)
end
function BuildPlayerCarousel(selectedPlayer, worldPoint)
local playersByProximity = {}
local players = PlayersService:GetPlayers()
for i = 1, #players do
if players[i].UserId > 0 then
if players[i] ~= LocalPlayer then
local playerPosition = ContextMenuUtil:GetPlayerPosition(players[i])
if playerPosition then
local distanceFromClicked = (worldPoint - playerPosition).magnitude
table.insert(playersByProximity, {players[i], distanceFromClicked})
end
end
end
end
local function closestPlayerComp(playerA, playerB)
return playerA[2] > playerB[2]
end
table.sort(playersByProximity, closestPlayerComp)
ContextMenuGui:BuildPlayerCarousel(playersByProximity)
end
function OpenContextMenu(player, worldPoint)
if ContextMenuOpening or ContextMenuOpen or not isAvatarContextMenuEnabled then
return
end
ContextMenuOpen = true
BuildPlayerCarousel(player, worldPoint)
ContextMenuUtil:DisablePlayerMovement()
BindMenuActions()
SetSelectedPlayer(player, true)
OpenMenu()
end
function CloseContextMenu()
GuiService.SelectedCoreObject = nil
ContextMenuUtil:EnablePlayerMovement()
if contextMenuPlayerChangedConn then
contextMenuPlayerChangedConn:disconnect()
end
local positionTween = TweenService:Create(ContextMenuFrame, CLOSE_MENU_TWEEN, {Position = UDim2.new(0.5, 0, 1, ContextMenuFrame.AbsoluteSize.Y)})
positionTween:Play()
positionTween.Completed:wait()
ContextMenuFrame.Visible = false
SetSelectedPlayer(nil)
ContextMenuOpen = false
end
ContextMenuGui:SetCloseMenuFunc(CloseContextMenu)
ContextMenuItems:SetCloseMenuFunc(CloseContextMenu)
local function isPointInside(point, topLeft, bottomRight)
return (point.X >= topLeft.X and
point.X <= bottomRight.X and
point.Y >= topLeft.Y and
point.Y <= bottomRight.Y)
end
function PointInSwipeArea(screenPoint)
local topLeft = ContextMenuFrame.AbsolutePosition
local nameTag = ContextMenuFrame.Content:FindFirstChild("NameTag")
local bottomRight = Vector2.new(topLeft.x + ContextMenuFrame.AbsoluteSize.x, nameTag.AbsolutePosition.y + nameTag.AbsoluteSize.y)
return isPointInside(screenPoint, topLeft, bottomRight)
end
function LocalPlayerHasToolEquipped()
if not LocalPlayer.Character then return false end
for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
if child:IsA("BackpackItem") then
return true
end
end
return false
end
function clickedOnPoint(screenPoint)
local camera = workspace.CurrentCamera
if not camera then return end
if LocalPlayerHasToolEquipped() then return end
local ray = camera:ScreenPointToRay(screenPoint.X, screenPoint.Y)
ray = Ray.new(ray.Origin, ray.Direction * MAX_CONTEXT_MENU_DISTANCE)
local hitPart, hitPoint = workspace:FindPartOnRay(ray, nil, false, true)
local player = ContextMenuUtil:FindPlayerFromPart(hitPart)
if player and ((DEBUG_MODE and player ~= LocalPlayer) or (player ~= LocalPlayer and player.UserId > 0)) then
if ContextMenuOpen then
SetSelectedPlayer(player)
else
OpenContextMenu(player, hitPoint)
end
elseif not player and ContextMenuOpen then
CloseContextMenu()
end
end
function OnUserInput(screenPoint, inputObject)
if inputObject.UserInputState == Enum.UserInputState.Begin and lastInputObject == nil then
lastInputObject = inputObject
initialScreenPoint = screenPoint
elseif lastInputObject == inputObject and inputObject.UserInputState == Enum.UserInputState.Change then
if (screenPoint - initialScreenPoint).magnitude > 5 then
lastInputObject = nil
initialScreenPoint = nil
end
elseif inputObject.UserInputState == Enum.UserInputState.End and lastInputObject == inputObject then
lastInputObject = nil
initialScreenPoint = nil
clickedOnPoint(screenPoint)
end
end
function OnMouseMoved(screenPoint)
if not ContextMenuOpen and lastInputObject and (screenPoint - initialScreenPoint).magnitude > MAX_MOVEMENT_THRESHOLD then
lastInputObject = nil
initialScreenPoint = nil
end
end
function trackTouchSwipeInput(inputObject)
if inputObject.UserInputType == Enum.UserInputType.Touch then
if not hasTouchSwipeInput and inputObject.UserInputState == Enum.UserInputState.Begin then
if PointInSwipeArea(inputObject.Position) then
hasTouchSwipeInput = inputObject
end
elseif hasTouchSwipeInput == inputObject and inputObject.UserInputState == Enum.UserInputState.End then
spawn(function()
hasTouchSwipeInput = nil
end)
end
end
end
local function functionProcessInput(inputObject, gameProcessedEvent)
trackTouchSwipeInput(inputObject)
if gameProcessedEvent then return end
if inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
inputObject.UserInputType == Enum.UserInputType.Touch then
OnUserInput(Vector2.new(inputObject.Position.X, inputObject.Position.Y), inputObject)
elseif inputObject.UserInputType == Enum.UserInputType.MouseMovement then
OnMouseMoved(Vector2.new(inputObject.Position.X, inputObject.Position.Y))
end
end
UserInputService.InputBegan:Connect(functionProcessInput)
UserInputService.InputChanged:Connect(functionProcessInput)
UserInputService.InputEnded:Connect(functionProcessInput)
UserInputService.TouchSwipe:Connect(function(swipeDir, numOfTouches, gameProcessedEvent)
if not gameProcessedEvent then return end
if not ContextMenuOpen then return end
if not hasTouchSwipeInput then return end
local offset = 0
if swipeDir == Enum.SwipeDirection.Left then
offset = 1
elseif swipeDir == Enum.SwipeDirection.Right then
offset = -1
end
if offset ~= 0 then
ContextMenuGui:OffsetPlayerEntry(offset)
SetSelectedPlayer(ContextMenuGui:GetSelectedPlayer())
end
end)
LocalPlayer.FriendStatusChanged:Connect(function(player, friendStatus)
if player and player == SelectedPlayer then
ContextMenuItems:UpdateFriendButton(friendStatus)
end
end)
@@ -0,0 +1,173 @@
--[[
// Filename: BlockPlayerPrompt.lua
// Version 1.0
// Written by: TheGamer101
// Description: Handles prompting the blocking and unblocking of Players.
]]--
local StarterGui = game:GetService("StarterGui")
local PlayersService = game:GetService("Players")
local CoreGuiService = game:GetService("CoreGui")
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local LocalPlayer = PlayersService.LocalPlayer
while LocalPlayer == nil do
PlayersService.ChildAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local PromptCreator = require(CoreGuiModules:WaitForChild("PromptCreator"))
local SocialUtil = require(CoreGuiModules:WaitForChild("SocialUtil"))
local PlayerDropDownModule = require(CoreGuiModules:WaitForChild("PlayerDropDown"))
local BlockingUtility = PlayerDropDownModule:CreateBlockingUtility()
local THUMBNAIL_URL = "https://www.roblox.com/Thumbs/Avatar.ashx?x=200&y=200&format=png&userId="
local BUST_THUMBNAIL_URL = "https://www.roblox.com/bust-thumbnail/image?width=420&height=420&format=png&userId="
local REGULAR_THUMBNAIL_IMAGE_SIZE = Enum.ThumbnailSize.Size150x150
local CONSOLE_THUMBNAIL_IMAGE_SIZE = Enum.ThumbnailSize.Size352x352
local REGULAR_THUMBNAIL_IMAGE_TYPE = Enum.ThumbnailType.HeadShot
local CONSOLE_THUMBNAIL_IMAGE_TYPE = Enum.ThumbnailType.AvatarThumbnail
-- fetch and store player block list
PlayerDropDownModule:InitBlockListAsync()
function createFetchImageFunction(...)
local args = {...}
return function(imageLabel)
spawn(function()
local imageUrl = SocialUtil.GetPlayerImage(unpack(args))
if imageLabel and imageLabel.Parent then
imageLabel.Image = imageUrl
end
end)
end
end
function DoPromptBlockPlayer(playerToBlock)
if BlockingUtility:IsPlayerBlockedByUserId(playerToBlock.UserId) then
return
end
local function promptCompletedCallback(clickedConfirm)
if clickedConfirm then
local successfullyBlocked = BlockingUtility:BlockPlayerAsync(playerToBlock)
if not successfullyBlocked then
while PromptCreator:IsCurrentlyPrompting() do
wait()
end
PromptCreator:CreatePrompt({
WindowTitle = "Error Blocking Player",
MainText = string.format("An error occurred while blocking %s. Please try again later.", playerToBlock.Name),
ConfirmationText = "Okay",
CancelActive = false,
Image = BUST_THUMBNAIL_URL ..playerToBlock.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToBlock.UserId,
FetchImageFunction = createFetchImageFunction(playerToBlock.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToBlock.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
end
end
end
PromptCreator:CreatePrompt({
WindowTitle = "Confirm Block",
MainText = string.format("Are you sure you want to block %s?", playerToBlock.Name),
ConfirmationText = "Block",
CancelText = "Cancel",
CancelActive = true,
Image = BUST_THUMBNAIL_URL ..playerToBlock.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToBlock.UserId,
FetchImageFunction = createFetchImageFunction(playerToBlock.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToBlock.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
PromptCompletedCallback = promptCompletedCallback,
})
end
function PromptBlockPlayer(player)
if LocalPlayer.UserId < 0 then
error("PromptBlockPlayer can not be called for guests!")
end
if typeof(player) == "Instance" and player:IsA("Player") then
if player.UserId < 0 then
error("PromptBlockPlayer can not be called on guests!")
end
if player == LocalPlayer then
error("PromptBlockPlayer: A user can not block themselves!")
end
DoPromptBlockPlayer(player)
else
error("Invalid argument to PromptBlockPlayer")
end
end
function DoPromptUnblockPlayer(playerToUnblock)
if not BlockingUtility:IsPlayerBlockedByUserId(playerToUnblock.UserId) and false then
return
end
local function promptCompletedCallback(clickedConfirm)
if clickedConfirm then
local successfullyUnblocked = BlockingUtility:UnblockPlayerAsync(playerToUnblock)
if not successfullyUnblocked then
while PromptCreator:IsCurrentlyPrompting() do
wait()
end
PromptCreator:CreatePrompt({
WindowTitle = "Error Unblocking Player",
MainText = string.format("An error occurred while unblocking %s. Please try again later.", playerToUnblock.Name),
ConfirmationText = "Okay",
Image = BUST_THUMBNAIL_URL ..playerToUnblock.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToUnblock.UserId,
FetchImageFunction = createFetchImageFunction(playerToUnblock.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToUnblock.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
end
end
end
PromptCreator:CreatePrompt({
WindowTitle = "Confirm Unblock",
MainText = string.format("Would you like to unblock %s?", playerToUnblock.Name),
ConfirmationText = "Unblock",
CancelText = "Cancel",
CancelActive = true,
Image = BUST_THUMBNAIL_URL ..playerToUnblock.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToUnblock.UserId,
FetchImageFunction = createFetchImageFunction(playerToUnblock.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToUnblock.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
PromptCompletedCallback = promptCompletedCallback,
})
end
function PromptUnblockPlayer(player)
if LocalPlayer.UserId < 0 then
error("PromptUnblockPlayer can not be called for guests!")
end
if typeof(player) == "Instance" and player:IsA("Player") then
if player.UserId < 0 then
error("PromptUnblockPlayer can not be called on guests!")
end
if player == LocalPlayer then
error("PromptUnblockPlayer: A user can not block themselves!")
end
DoPromptUnblockPlayer(player)
else
error("Invalid argument to PromptUnblockPlayer")
end
end
function GetBlockedUserIds()
if LocalPlayer.UserId < 0 then
error("GetBlockedUserIds can not be called for guests!")
end
return BlockingUtility:GetBlockedUserIdsAsync()
end
StarterGui:RegisterSetCore("PromptBlockPlayer", PromptBlockPlayer)
StarterGui:RegisterSetCore("PromptUnblockPlayer", PromptUnblockPlayer)
StarterGui:RegisterGetCore("GetBlockedUserIds", GetBlockedUserIds)
@@ -0,0 +1,279 @@
-- ContextActionTouch.lua
-- Copyright ROBLOX 2014, created by Ben Tkacheff
-- this script controls ui and firing of lua functions that are bound in ContextActionService for touch inputs
-- Essentially a user can bind a lua function to a key code, input type (mousebutton1 etc.) and this
-- Variables
local contextActionService = game:GetService("ContextActionService")
local userInputService = game:GetService("UserInputService")
local playersService = game:GetService("Players")
local isTouchDevice = userInputService.TouchEnabled
local functionTable = {}
local buttonVector = {}
local buttonScreenGui = nil
local buttonFrame = nil
local ContextDownImage = "https://www.roblox.com/asset/?id=97166756"
local ContextUpImage = "https://www.roblox.com/asset/?id=97166444"
local oldTouches = {}
local buttonPositionTable = {
[1] = UDim2.new(0,123,0,70),
[2] = UDim2.new(0,30,0,60),
[3] = UDim2.new(0,180,0,160),
[4] = UDim2.new(0,85,0,-25),
[5] = UDim2.new(0,185,0,-25),
[6] = UDim2.new(0,185,0,260),
[7] = UDim2.new(0,216,0,65)
}
local maxButtons = #buttonPositionTable
-- Preload images
game:GetService("ContentProvider"):Preload(ContextDownImage)
game:GetService("ContentProvider"):Preload(ContextUpImage)
local localPlayer = playersService.LocalPlayer
while not localPlayer do
playersService.ChildAdded:wait()
localPlayer = playersService.LocalPlayer
end
function createContextActionGui()
if not buttonScreenGui and isTouchDevice then
buttonScreenGui = Instance.new("ScreenGui")
buttonScreenGui.Name = "ContextActionGui"
buttonScreenGui.AncestryChanged:connect(function(child, newParent)
if newParent == nil then
buttonScreenGui = nil
end
end)
buttonFrame = Instance.new("Frame")
buttonFrame.BackgroundTransparency = 1
buttonFrame.Size = UDim2.new(0.3,0,0.5,0)
buttonFrame.Position = UDim2.new(0.7,0,0.5,0)
buttonFrame.Name = "ContextButtonFrame"
buttonFrame.Parent = buttonScreenGui
buttonFrame.Visible = not userInputService.ModalEnabled
userInputService.Changed:connect(function(property)
if property == "ModalEnabled" then
buttonFrame.Visible = not userInputService.ModalEnabled
end
end)
end
end
-- functions
function setButtonSizeAndPosition(object)
local buttonSize = 55
local xOffset = 10
local yOffset = 95
-- todo: better way to determine mobile sized screens
local onSmallScreen = (game:GetService("CoreGui").RobloxGui.AbsoluteSize.X < 600)
if not onSmallScreen then
buttonSize = 85
xOffset = 40
end
object.Size = UDim2.new(0,buttonSize,0,buttonSize)
end
function contextButtonDown(button, inputObject, actionName)
if inputObject.UserInputType == Enum.UserInputType.Touch then
button.Image = ContextDownImage
contextActionService:CallFunction(actionName, Enum.UserInputState.Begin, inputObject)
end
end
function contextButtonMoved(button, inputObject, actionName)
if inputObject.UserInputType == Enum.UserInputType.Touch then
button.Image = ContextDownImage
contextActionService:CallFunction(actionName, Enum.UserInputState.Change, inputObject)
end
end
function contextButtonUp(button, inputObject, actionName)
button.Image = ContextUpImage
if inputObject.UserInputType == Enum.UserInputType.Touch and inputObject.UserInputState == Enum.UserInputState.End then
contextActionService:CallFunction(actionName, Enum.UserInputState.End, inputObject)
end
end
function isSmallScreenDevice()
return game:GetService("GuiService"):GetScreenResolution().y <= 320
end
function createNewButton(actionName, functionInfoTable)
local contextButton = Instance.new("ImageButton")
contextButton.Name = "ContextActionButton"
contextButton.BackgroundTransparency = 1
contextButton.Size = UDim2.new(0,45,0,45)
contextButton.Active = true
if isSmallScreenDevice() then
contextButton.Size = UDim2.new(0,35,0,35)
end
contextButton.Image = ContextUpImage
contextButton.Parent = buttonFrame
local currentButtonTouch = nil
userInputService.InputEnded:connect(function ( inputObject )
oldTouches[inputObject] = nil
end)
contextButton.InputBegan:connect(function(inputObject)
if oldTouches[inputObject] then return end
if inputObject.UserInputState == Enum.UserInputState.Begin and currentButtonTouch == nil then
currentButtonTouch = inputObject
contextButtonDown(contextButton, inputObject, actionName)
end
end)
contextButton.InputChanged:connect(function(inputObject)
if oldTouches[inputObject] then return end
if currentButtonTouch ~= inputObject then return end
contextButtonMoved(contextButton, inputObject, actionName)
end)
contextButton.InputEnded:connect(function(inputObject)
if oldTouches[inputObject] then return end
if currentButtonTouch ~= inputObject then return end
currentButtonTouch = nil
oldTouches[inputObject] = true
contextButtonUp(contextButton, inputObject, actionName)
end)
local actionIcon = Instance.new("ImageLabel")
actionIcon.Name = "ActionIcon"
actionIcon.Position = UDim2.new(0.175, 0, 0.175, 0)
actionIcon.Size = UDim2.new(0.65, 0, 0.65, 0)
actionIcon.BackgroundTransparency = 1
if functionInfoTable["image"] and type(functionInfoTable["image"]) == "string" then
actionIcon.Image = functionInfoTable["image"]
end
actionIcon.Parent = contextButton
local actionTitle = Instance.new("TextLabel")
actionTitle.Name = "ActionTitle"
actionTitle.Size = UDim2.new(1,0,1,0)
actionTitle.BackgroundTransparency = 1
actionTitle.Font = Enum.Font.SourceSansBold
actionTitle.TextColor3 = Color3.new(1,1,1)
actionTitle.TextStrokeTransparency = 0
actionTitle.FontSize = Enum.FontSize.Size18
actionTitle.TextWrapped = true
actionTitle.Text = ""
if functionInfoTable["title"] and type(functionInfoTable["title"]) == "string" then
actionTitle.Text = functionInfoTable["title"]
end
actionTitle.Parent = contextButton
return contextButton
end
function createButton( actionName, functionInfoTable )
local button = createNewButton(actionName, functionInfoTable)
local position = nil
for i = 1,#buttonVector do
if buttonVector[i] == "empty" then
position = i
break
end
end
if not position then
position = #buttonVector + 1
end
if position > maxButtons then
return -- todo: let user know we have too many buttons already?
end
buttonVector[position] = button
functionTable[actionName]["button"] = button
button.Position = buttonPositionTable[position]
button.Parent = buttonFrame
if buttonScreenGui and buttonScreenGui.Parent == nil then
buttonScreenGui.Parent = localPlayer.PlayerGui
if not buttonFrame.Parent then
buttonFrame.Parent = buttonScreenGui
end
end
end
function removeAction(actionName)
if not functionTable[actionName] then return end
local actionButton = functionTable[actionName]["button"]
if actionButton then
actionButton.Parent = nil
for i = 1,#buttonVector do
if buttonVector[i] == actionButton then
buttonVector[i] = "empty"
break
end
end
actionButton:Destroy()
end
functionTable[actionName] = nil
end
function addAction(actionName,createTouchButton,functionInfoTable)
if functionTable[actionName] then
removeAction(actionName)
end
functionTable[actionName] = {functionInfoTable}
if createTouchButton and isTouchDevice then
createContextActionGui()
createButton(actionName, functionInfoTable)
end
end
-- Connections
contextActionService.BoundActionChanged:connect( function(actionName, changeName, changeTable)
if functionTable[actionName] and changeTable then
local button = functionTable[actionName]["button"]
if button then
if changeName == "image" then
button.ActionIcon.Image = changeTable[changeName]
elseif changeName == "title" then
button.ActionTitle.Text = changeTable[changeName]
elseif changeName == "description" then
-- todo: add description to menu
elseif changeName == "position" then
button.Position = changeTable[changeName]
end
end
end
end)
contextActionService.BoundActionAdded:connect( function(actionName, createTouchButton, functionInfoTable)
addAction(actionName, createTouchButton, functionInfoTable)
end)
contextActionService.BoundActionRemoved:connect( function(actionName, functionInfoTable)
removeAction(actionName)
end)
contextActionService.GetActionButtonEvent:connect( function(actionName)
if functionTable[actionName] then
contextActionService:FireActionButtonFoundSignal(actionName, functionTable[actionName]["button"])
end
end)
-- make sure any bound data before we setup connections is handled
local boundActions = contextActionService:GetAllBoundActionInfo()
for actionName, actionData in pairs(boundActions) do
addAction(actionName,actionData["createTouchButton"],actionData)
end
@@ -0,0 +1,286 @@
--[[
// Filename: FriendPlayerPrompt.lua
// Version 1.0
// Written by: TheGamer101
// Description: Can prompt a user to send a friend request or unfriend a player.
]]--
local StarterGui = game:GetService("StarterGui")
local PlayersService = game:GetService("Players")
local CoreGuiService = game:GetService("CoreGui")
local AnalyticsService = game:GetService("AnalyticsService")
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local LocalPlayer = PlayersService.LocalPlayer
while LocalPlayer == nil do
PlayersService.ChildAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local PromptCreator = require(CoreGuiModules:WaitForChild("PromptCreator"))
local SocialUtil = require(CoreGuiModules:WaitForChild("SocialUtil"))
local PlayerDropDownModule = require(CoreGuiModules:WaitForChild("PlayerDropDown"))
local FFlagCoreScriptsUseLocalizationModule = settings():GetFFlag('CoreScriptsUseLocalizationModule')
local FFlagFriendPlayerPromptUseFormatByKey = settings():GetFFlag('FriendPlayerPromptUseFormatByKey')
local RobloxTranslator
if FFlagCoreScriptsUseLocalizationModule then
RobloxTranslator = require(CoreGuiModules:WaitForChild("RobloxTranslator"))
end
local THUMBNAIL_URL = "https://www.roblox.com/Thumbs/Avatar.ashx?x=200&y=200&format=png&userId="
local BUST_THUMBNAIL_URL = "https://www.roblox.com/bust-thumbnail/image?width=420&height=420&format=png&userId="
local REGULAR_THUMBNAIL_IMAGE_SIZE = Enum.ThumbnailSize.Size150x150
local CONSOLE_THUMBNAIL_IMAGE_SIZE = Enum.ThumbnailSize.Size352x352
local REGULAR_THUMBNAIL_IMAGE_TYPE = Enum.ThumbnailType.HeadShot
local CONSOLE_THUMBNAIL_IMAGE_TYPE = Enum.ThumbnailType.AvatarThumbnail
local success, result = pcall(function() return settings():GetFFlag('UseNotificationsLocalization') end)
local FFlagUseNotificationsLocalization = success and result
local function LocalizedGetString(key, rtv)
pcall(function()
if FFlagCoreScriptsUseLocalizationModule then
rtv = RobloxTranslator:FormatByKey(key)
else
local LocalizationService = game:GetService("LocalizationService")
local CorescriptLocalization = LocalizationService:GetCorescriptLocalizations()[1]
rtv = CorescriptLocalization:GetString(LocalizationService.RobloxLocaleId, key)
end
end)
return rtv
end
function createFetchImageFunction(...)
local args = {...}
return function(imageLabel)
spawn(function()
local imageUrl = SocialUtil.GetPlayerImage(unpack(args))
if imageLabel and imageLabel.Parent then
imageLabel.Icon.Image = imageUrl
end
end)
end
end
function SendFriendRequest(playerToFriend)
AnalyticsService:ReportCounter("FriendPlayerPrompt-RequestFriendship")
AnalyticsService:TrackEvent("Game", "RequestFriendship", "FriendPlayerPrompt")
local success = pcall(function()
LocalPlayer:RequestFriendship(playerToFriend)
end)
return success
end
function AtFriendLimit(player)
local friendCount = PlayerDropDownModule:GetFriendCountAsync(player)
if friendCount == nil then
return false
end
if friendCount >= PlayerDropDownModule:MaxFriendCount() then
return true
end
return false
end
function DoPromptRequestFriendPlayer(playerToFriend)
if LocalPlayer:IsFriendsWith(playerToFriend.UserId) then
return
end
local function promptCompletedCallback(clickedConfirm)
if clickedConfirm then
if AtFriendLimit(LocalPlayer) then
while PromptCreator:IsCurrentlyPrompting() do
wait()
end
PromptCreator:CreatePrompt({
WindowTitle = "Friend Limit Reached",
MainText = "You can not send a friend request because you are at the max friend limit.",
ConfirmationText = "Okay",
CancelActive = false,
Image = BUST_THUMBNAIL_URL ..playerToFriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToFriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToFriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToFriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
else
if AtFriendLimit(playerToFriend) then
local mainText = string.format("You can not send a friend request to %s because they are at the max friend limit.", playerToFriend.Name)
if FFlagFriendPlayerPromptUseFormatByKey then
mainText = RobloxTranslator:FormatByKey("FriendPlayerPrompt.promptCompletedCallback.AtFriendLimit", {RBX_NAME = playerToFriend.Name})
else
if FFlagUseNotificationsLocalization then
mainText = string.gsub(LocalizedGetString("FriendPlayerPrompt.promptCompletedCallback.AtFriendLimit",mainText),"{RBX_NAME}",playerToFriend.Name)
end
end
PromptCreator:CreatePrompt({
WindowTitle = "Error Sending Friend Request",
MainText = mainText,
ConfirmationText = "Okay",
CancelActive = false,
Image = BUST_THUMBNAIL_URL ..playerToFriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToFriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToFriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToFriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
else
local successfullySentFriendRequest = SendFriendRequest(playerToFriend)
if not successfullySentFriendRequest then
while PromptCreator:IsCurrentlyPrompting() do
wait()
end
local mainText = string.format("An error occurred while sending %s a friend request. Please try again later.", playerToFriend.Name)
if FFlagFriendPlayerPromptUseFormatByKey then
mainText = RobloxTranslator:FormatByKey("FriendPlayerPrompt.promptCompletedCallback.UnknownError", {RBX_NAME = playerToFriend.Name})
else
if FFlagUseNotificationsLocalization then
mainText = string.gsub(LocalizedGetString("FriendPlayerPrompt.promptCompletedCallback.UnknownError",mainText),"{RBX_NAME}",playerToFriend.Name)
end
end
PromptCreator:CreatePrompt({
WindowTitle = "Error Sending Friend Request",
MainText = mainText,
ConfirmationText = "Okay",
CancelActive = false,
Image = BUST_THUMBNAIL_URL ..playerToFriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToFriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToFriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToFriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
end
end
end
end
end
local mainText = string.format("Would you like to send %s a Friend Request?", playerToFriend.Name)
if FFlagFriendPlayerPromptUseFormatByKey then
mainText = RobloxTranslator:FormatByKey("FriendPlayerPrompt.DoPromptRequestFriendPlayer", {RBX_NAME = playerToFriend.Name})
else
if FFlagUseNotificationsLocalization then
mainText = string.gsub(LocalizedGetString("FriendPlayerPrompt.DoPromptRequestFriendPlayer",mainText),"{RBX_NAME}",playerToFriend.Name)
end
end
PromptCreator:CreatePrompt({
WindowTitle = "Send Friend Request?",
MainText = mainText,
ConfirmationText = "Send Request",
CancelText = "Cancel",
CancelActive = true,
Image = BUST_THUMBNAIL_URL ..playerToFriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToFriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToFriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToFriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
PromptCompletedCallback = promptCompletedCallback,
})
end
function PromptRequestFriendPlayer(player)
if LocalPlayer.UserId < 0 then
error("PromptSendFriendRequest can not be called for guests!")
end
if typeof(player) == "Instance" and player:IsA("Player") then
if player.UserId < 0 then
error("PromptSendFriendRequest can not be called on guests!")
end
if player == LocalPlayer then
error("PromptSendFriendRequest: A user can not friend themselves!")
end
DoPromptRequestFriendPlayer(player)
else
error("Invalid argument to PromptSendFriendRequest")
end
end
function UnFriendPlayer(playerToUnfriend)
local success = pcall(function()
LocalPlayer:RevokeFriendship(playerToUnfriend)
end)
return success
end
function DoPromptUnfriendPlayer(playerToUnfriend)
if not LocalPlayer:IsFriendsWith(playerToUnfriend.UserId) then
return
end
local function promptCompletedCallback(clickedConfirm)
if clickedConfirm then
local successfullyUnfriended = UnFriendPlayer(playerToUnfriend)
if not successfullyUnfriended then
while PromptCreator:IsCurrentlyPrompting() do
wait()
end
local mainText = string.format("An error occurred while unfriending %s. Please try again later.", playerToUnfriend.Name)
if FFlagUseNotificationsLocalization then
mainText = string.gsub(LocalizedGetString("FriendPlayerPrompt.promptCompletedCallback.UnknownError",mainText),"{RBX_NAME}",playerToUnfriend.Name)
end
PromptCreator:CreatePrompt({
WindowTitle = "Error Unfriending Player",
MainText = mainText,
ConfirmationText = "Okay",
CancelActive = false,
Image = BUST_THUMBNAIL_URL ..playerToUnfriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToUnfriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToUnfriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToUnfriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
StripeColor = Color3.fromRGB(183, 34, 54),
})
end
end
end
local mainText = string.format("Would you like to remove %s from your friends list?", playerToUnfriend.Name)
if FFlagUseNotificationsLocalization then
mainText = string.gsub(LocalizedGetString("FriendPlayerPrompt.DoPromptUnfriendPlayer",mainText),"{RBX_NAME}",playerToUnfriend.Name)
end
PromptCreator:CreatePrompt({
WindowTitle = "Unfriend Player?",
MainText = mainText,
ConfirmationText = "Unfriend",
CancelText = "Cancel",
CancelActive = true,
Image = BUST_THUMBNAIL_URL ..playerToUnfriend.UserId,
ImageConsoleVR = THUMBNAIL_URL ..playerToUnfriend.UserId,
FetchImageFunction = createFetchImageFunction(playerToUnfriend.UserId, REGULAR_THUMBNAIL_IMAGE_SIZE, REGULAR_THUMBNAIL_IMAGE_TYPE),
FetchImageFunctionConsoleVR = createFetchImageFunction(playerToUnfriend.UserId, CONSOLE_THUMBNAIL_IMAGE_SIZE, CONSOLE_THUMBNAIL_IMAGE_TYPE),
PromptCompletedCallback = promptCompletedCallback,
})
end
function PromptUnfriendPlayer(player)
if LocalPlayer.UserId < 0 then
error("PromptUnfriend can not be called for guests!")
end
if typeof(player) == "Instance" and player:IsA("Player") then
if player.UserId < 0 then
error("PromptUnfriend can not be called on guests!")
end
if player == LocalPlayer then
error("PromptUnfriend: A user can not unfriend themselves!")
end
DoPromptUnfriendPlayer(player)
else
error("Invalid argument to PromptUnfriend")
end
end
StarterGui:RegisterSetCore("PromptSendFriendRequest", PromptRequestFriendPlayer)
StarterGui:RegisterSetCore("PromptUnfriend", PromptUnfriendPlayer)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,310 @@
--[[
This script controls the gui the player sees in regards to his or her health.
Can be turned with Game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Health,false)
Copyright ROBLOX 2014. Written by Ben Tkacheff.
--]]
---------------------------------------------------------------------
-- Initialize/Variables
while not game do
wait(1/60)
end
while not game:GetService("Players") do
wait(1/60)
end
local currentHumanoid = nil
local HealthGui = nil
local lastHealth = 100
local HealthPercentageForOverlay = 5
local maxBarTweenTime = 0.3
local greenColor = Color3.new(0.2, 1, 0.2)
local redColor = Color3.new(1, 0.2, 0.2)
local yellowColor = Color3.new(1, 1, 0.2)
local guiEnabled = false
local healthChangedConnection = nil
local humanoidDiedConnection = nil
local characterAddedConnection = nil
local greenBarImage = "rbxasset://textures/ui/Health-BKG-Center.png"
local greenBarImageLeft = "rbxasset://textures/ui/Health-BKG-Left-Cap.png"
local greenBarImageRight = "rbxasset://textures/ui/Health-BKG-Right-Cap.png"
local hurtOverlayImage = "https://www.roblox.com/asset/?id=34854607"
game:GetService("ContentProvider"):Preload(greenBarImage)
game:GetService("ContentProvider"):Preload(hurtOverlayImage)
while not game:GetService("Players").LocalPlayer do
wait(1/60)
end
---------------------------------------------------------------------
-- Functions
local capHeight = 15
local capWidth = 7
function CreateGui()
if HealthGui and #HealthGui:GetChildren() > 0 then
HealthGui.Parent = game:GetService("CoreGui").RobloxGui
return
end
local hurtOverlay = Instance.new("ImageLabel")
hurtOverlay.Name = "HurtOverlay"
hurtOverlay.BackgroundTransparency = 1
hurtOverlay.Image = hurtOverlayImage
hurtOverlay.Position = UDim2.new(-10,0,-10,0)
hurtOverlay.Size = UDim2.new(20,0,20,0)
hurtOverlay.Visible = false
hurtOverlay.Parent = HealthGui
local healthFrame = Instance.new("Frame")
healthFrame.Name = "HealthFrame"
healthFrame.BackgroundTransparency = 1
healthFrame.BackgroundColor3 = Color3.new(1,1,1)
healthFrame.BorderColor3 = Color3.new(0,0,0)
healthFrame.BorderSizePixel = 0
healthFrame.Position = UDim2.new(0.5,-85,1,-20)
healthFrame.Size = UDim2.new(0,170,0,capHeight)
healthFrame.Parent = HealthGui
local healthBarBackCenter = Instance.new("ImageLabel")
healthBarBackCenter.Name = "healthBarBackCenter"
healthBarBackCenter.BackgroundTransparency = 1
healthBarBackCenter.Image = greenBarImage
healthBarBackCenter.Size = UDim2.new(1,-capWidth*2,1,0)
healthBarBackCenter.Position = UDim2.new(0,capWidth,0,0)
healthBarBackCenter.Parent = healthFrame
healthBarBackCenter.ImageColor3 = Color3.new(1,1,1)
local healthBarBackLeft = Instance.new("ImageLabel")
healthBarBackLeft.Name = "healthBarBackLeft"
healthBarBackLeft.BackgroundTransparency = 1
healthBarBackLeft.Image = greenBarImageLeft
healthBarBackLeft.Size = UDim2.new(0,capWidth,1,0)
healthBarBackLeft.Position = UDim2.new(0,0,0,0)
healthBarBackLeft.Parent = healthFrame
healthBarBackLeft.ImageColor3 = Color3.new(1,1,1)
local healthBarBackRight = Instance.new("ImageLabel")
healthBarBackRight.Name = "healthBarBackRight"
healthBarBackRight.BackgroundTransparency = 1
healthBarBackRight.Image = greenBarImageRight
healthBarBackRight.Size = UDim2.new(0,capWidth,1,0)
healthBarBackRight.Position = UDim2.new(1,-capWidth,0,0)
healthBarBackRight.Parent = healthFrame
healthBarBackRight.ImageColor3 = Color3.new(1,1,1)
local healthBar = Instance.new("Frame")
healthBar.Name = "HealthBar"
healthBar.BackgroundTransparency = 1
healthBar.BackgroundColor3 = Color3.new(1,1,1)
healthBar.BorderColor3 = Color3.new(0,0,0)
healthBar.BorderSizePixel = 0
healthBar.ClipsDescendants = true
healthBar.Position = UDim2.new(0, 0, 0, 0)
healthBar.Size = UDim2.new(1,0,1,0)
healthBar.Parent = healthFrame
local healthBarCenter = Instance.new("ImageLabel")
healthBarCenter.Name = "healthBarCenter"
healthBarCenter.BackgroundTransparency = 1
healthBarCenter.Image = greenBarImage
healthBarCenter.Size = UDim2.new(1,-capWidth*2,1,0)
healthBarCenter.Position = UDim2.new(0,capWidth,0,0)
healthBarCenter.Parent = healthBar
healthBarCenter.ImageColor3 = greenColor
local healthBarLeft = Instance.new("ImageLabel")
healthBarLeft.Name = "healthBarLeft"
healthBarLeft.BackgroundTransparency = 1
healthBarLeft.Image = greenBarImageLeft
healthBarLeft.Size = UDim2.new(0,capWidth,1,0)
healthBarLeft.Position = UDim2.new(0,0,0,0)
healthBarLeft.Parent = healthBar
healthBarLeft.ImageColor3 = greenColor
local healthBarRight = Instance.new("ImageLabel")
healthBarRight.Name = "healthBarRight"
healthBarRight.BackgroundTransparency = 1
healthBarRight.Image = greenBarImageRight
healthBarRight.Size = UDim2.new(0,capWidth,1,0)
healthBarRight.Position = UDim2.new(1,-capWidth,0,0)
healthBarRight.Parent = healthBar
healthBarRight.ImageColor3 = greenColor
HealthGui.Parent = game:GetService("CoreGui").RobloxGui
end
function UpdateGui(health)
if not HealthGui then return end
local healthFrame = HealthGui:FindFirstChild("HealthFrame")
if not healthFrame then return end
local healthBar = healthFrame:FindFirstChild("HealthBar")
if not healthBar then return end
-- If more than 1/4 health, bar = green. Else, bar = red.
local percentHealth = (health/currentHumanoid.MaxHealth)
if percentHealth ~= percentHealth then
percentHealth = 1
healthBar.healthBarCenter.ImageColor3 = yellowColor
healthBar.healthBarRight.ImageColor3 = yellowColor
healthBar.healthBarLeft.ImageColor3 = yellowColor
elseif percentHealth > 0.25 then
healthBar.healthBarCenter.ImageColor3 = greenColor
healthBar.healthBarRight.ImageColor3 = greenColor
healthBar.healthBarLeft.ImageColor3 = greenColor
else
healthBar.healthBarCenter.ImageColor3 = redColor
healthBar.healthBarRight.ImageColor3 = redColor
healthBar.healthBarLeft.ImageColor3 = redColor
end
local width = (health / currentHumanoid.MaxHealth)
width = math.max(math.min(width,1),0) -- make sure width is between 0 and 1
if width ~= width then width = 1 end
local healthDelta = lastHealth - health
lastHealth = health
local percentOfTotalHealth = math.abs(healthDelta/currentHumanoid.MaxHealth)
percentOfTotalHealth = math.max(math.min(percentOfTotalHealth,1),0) -- make sure percentOfTotalHealth is between 0 and 1
if percentOfTotalHealth ~= percentOfTotalHealth then percentOfTotalHealth = 1 end
local newHealthSize = UDim2.new(width,0,1,0)
healthBar.Size = newHealthSize
local sizeX = healthBar.AbsoluteSize.X
if sizeX < capWidth then
healthBar.healthBarCenter.Visible = false
healthBar.healthBarRight.Visible = false
elseif sizeX < (2*capWidth + 1) then
healthBar.healthBarCenter.Visible = true
healthBar.healthBarCenter.Size = UDim2.new(0,sizeX - capWidth,1,0)
healthBar.healthBarRight.Visible = false
else
healthBar.healthBarCenter.Visible = true
healthBar.healthBarCenter.Size = UDim2.new(1,-capWidth*2,1,0)
healthBar.healthBarRight.Visible = true
end
local thresholdForHurtOverlay = currentHumanoid.MaxHealth * (HealthPercentageForOverlay/100)
if healthDelta >= thresholdForHurtOverlay and guiEnabled then
AnimateHurtOverlay()
end
end
function AnimateHurtOverlay()
if not HealthGui then return end
local overlay = HealthGui:FindFirstChild("HurtOverlay")
if not overlay then return end
local newSize = UDim2.new(20, 0, 20, 0)
local newPos = UDim2.new(-10, 0, -10, 0)
if overlay:IsDescendantOf(game) then
-- stop any tweens on overlay
overlay:TweenSizeAndPosition(newSize,newPos,Enum.EasingDirection.Out,Enum.EasingStyle.Linear,0,true,function()
-- show the gui
overlay.Size = UDim2.new(1,0,1,0)
overlay.Position = UDim2.new(0,0,0,0)
overlay.Visible = true
-- now tween the hide
if overlay:IsDescendantOf(game) then
overlay:TweenSizeAndPosition(newSize,newPos,Enum.EasingDirection.Out,Enum.EasingStyle.Quad,10,false,function()
overlay.Visible = false
end)
else
overlay.Size = newSize
overlay.Position = newPos
end
end)
else
overlay.Size = newSize
overlay.Position = newPos
end
end
function humanoidDied()
UpdateGui(0)
end
function disconnectPlayerConnections()
if characterAddedConnection then characterAddedConnection:disconnect() end
if humanoidDiedConnection then humanoidDiedConnection:disconnect() end
if healthChangedConnection then healthChangedConnection:disconnect() end
end
function newPlayerCharacter()
disconnectPlayerConnections()
startGui()
end
function startGui()
characterAddedConnection = game:GetService("Players").LocalPlayer.CharacterAdded:connect(newPlayerCharacter)
local character = game:GetService("Players").LocalPlayer.Character
if not character then
return
end
currentHumanoid = character:WaitForChild("Humanoid")
if not currentHumanoid then
return
end
if not game:GetService("StarterGui"):GetCoreGuiEnabled(Enum.CoreGuiType.Health) then
return
end
healthChangedConnection = currentHumanoid.HealthChanged:connect(UpdateGui)
humanoidDiedConnection = currentHumanoid.Died:connect(humanoidDied)
UpdateGui(currentHumanoid.Health)
CreateGui()
end
---------------------------------------------------------------------
-- Start Script
HealthGui = Instance.new("Frame")
HealthGui.Name = "HealthGui"
HealthGui.BackgroundTransparency = 1
HealthGui.Size = UDim2.new(1,0,1,0)
game:GetService("StarterGui").CoreGuiChangedSignal:connect(function(coreGuiType,enabled)
if coreGuiType == Enum.CoreGuiType.Health or coreGuiType == Enum.CoreGuiType.All then
if guiEnabled and not enabled then
if HealthGui then
HealthGui.Parent = nil
end
disconnectPlayerConnections()
elseif not guiEnabled and enabled then
startGui()
end
guiEnabled = enabled
end
end)
if game:GetService("StarterGui"):GetCoreGuiEnabled(Enum.CoreGuiType.Health) then
guiEnabled = true
startGui()
end
@@ -0,0 +1,769 @@
local PURPOSE_DATA = {
[Enum.DialogPurpose.Quest] = {
"rbxasset://textures/ui/dialog_purpose_quest.png",
Vector2.new(10, 34)
},
[Enum.DialogPurpose.Help] = {
"rbxasset://textures/ui/dialog_purpose_help.png",
Vector2.new(20, 35)
},
[Enum.DialogPurpose.Shop] = {
"rbxasset://textures/ui/dialog_purpose_shop.png",
Vector2.new(22, 43)
},
}
local TEXT_HEIGHT = 24 -- Pixel height of one row
local FONT_SIZE = Enum.FontSize.Size24
local BAR_THICKNESS = 6
local STYLE_PADDING = 17
local CHOICE_PADDING = 6 * 2 -- (Added to vertical height)
local PROMPT_SIZE = Vector2.new(80, 90)
local FRAME_WIDTH = 350
local WIDTH_BONUS = (STYLE_PADDING * 2) - BAR_THICKNESS
local XPOS_OFFSET = -(STYLE_PADDING - BAR_THICKNESS)
local playerService = game:GetService("Players")
local contextActionService = game:GetService("ContextActionService")
local guiService = game:GetService("GuiService")
local AnalyticsService = game:GetService("AnalyticsService")
local YPOS_OFFSET = -math.floor(STYLE_PADDING / 2)
local usingGamepad = false
local FlagHasReportedPlace = false
local FFlagWeDontWantAnyGoogleAnalyticsHerePlease = settings():GetFFlag("WeDontWantAnyGoogleAnalyticsHerePlease")
local localPlayer = playerService.LocalPlayer
while localPlayer == nil do
playerService.PlayerAdded:wait()
localPlayer = playerService.LocalPlayer
end
function setUsingGamepad(input, processed)
if input.UserInputType == Enum.UserInputType.Gamepad1 or input.UserInputType == Enum.UserInputType.Gamepad2 or
input.UserInputType == Enum.UserInputType.Gamepad3 or input.UserInputType == Enum.UserInputType.Gamepad4 then
usingGamepad = true
else
usingGamepad = false
end
end
game:GetService("UserInputService").InputBegan:connect(setUsingGamepad)
game:GetService("UserInputService").InputChanged:connect(setUsingGamepad)
FFlagCoreScriptTranslateGameText2 = settings():GetFFlag("CoreScriptTranslateGameText2")
function waitForProperty(instance, name)
while not instance[name] do
instance.Changed:wait()
end
end
local goodbyeChoiceActiveFlagSuccess, goodbyeChoiceActiveFlagValue = pcall(function()
return settings():GetFFlag("GoodbyeChoiceActiveProperty")
end)
local goodbyeChoiceActiveFlag = (goodbyeChoiceActiveFlagSuccess and goodbyeChoiceActiveFlagValue)
local mainFrame
local choices = {}
local lastChoice
local choiceMap = {}
local currentConversationDialog
local currentConversationPartner
local currentAbortDialogScript
local coroutineMap = {}
local currentDialogTimeoutCoroutine = nil
local tooFarAwayMessage = "You are too far away to chat!"
local tooFarAwaySize = 300
local characterWanderedOffMessage = "Chat ended because you walked away"
local characterWanderedOffSize = 350
local conversationTimedOut = "Chat ended because you didn't reply"
local conversationTimedOutSize = 350
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local RobloxReplicatedStorage = game:GetService('RobloxReplicatedStorage')
local setDialogInUseEvent = RobloxReplicatedStorage:WaitForChild("SetDialogInUse", 86400)
local player
local screenGui
local chatNotificationGui
local messageDialog
local timeoutScript
local reenableDialogScript
local dialogMap = {}
local dialogConnections = {}
local touchControlGui = nil
local gui = nil
local isTenFootInterface = require(RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")):IsEnabled()
local utility = require(RobloxGui.Modules.Settings.Utility)
local GameTranslator = require(RobloxGui.Modules.GameTranslator)
local isSmallTouchScreen = utility:IsSmallTouchScreen()
if isTenFootInterface then
FONT_SIZE = Enum.FontSize.Size36
TEXT_HEIGHT = 36
FRAME_WIDTH = 500
elseif isSmallTouchScreen then
FONT_SIZE = Enum.FontSize.Size14
TEXT_HEIGHT = 14
FRAME_WIDTH = 250
end
if RobloxGui:FindFirstChild("ControlFrame") then
gui = RobloxGui.ControlFrame
else
gui = RobloxGui
end
local touchEnabled = game:GetService("UserInputService").TouchEnabled
local function isDialogMultiplePlayers(dialog)
local success, value = pcall(function() return dialog.BehaviorType == Enum.DialogBehaviorType.MultiplePlayers end)
return success and value or false
end
function currentTone()
if currentConversationDialog then
return currentConversationDialog.Tone
else
return Enum.DialogTone.Neutral
end
end
function createChatNotificationGui()
chatNotificationGui = Instance.new("BillboardGui")
chatNotificationGui.Name = "ChatNotificationGui"
chatNotificationGui.ExtentsOffset = Vector3.new(0, 1, 0)
chatNotificationGui.Size = UDim2.new(PROMPT_SIZE.X / 31.5, 0, PROMPT_SIZE.Y / 31.5, 0)
chatNotificationGui.SizeOffset = Vector2.new(0, 0)
chatNotificationGui.StudsOffset = Vector3.new(0, 3.7, 0)
chatNotificationGui.Enabled = true
chatNotificationGui.RobloxLocked = true
chatNotificationGui.Active = true
local button = Instance.new("ImageButton")
button.Name = "Background"
button.Active = false
button.BackgroundTransparency = 1
button.Position = UDim2.new(0, 0, 0, 0)
button.Size = UDim2.new(1, 0, 1, 0)
button.Image = ""
button.Parent = chatNotificationGui
local icon = Instance.new("ImageLabel")
icon.Name = "Icon"
icon.Position = UDim2.new(0, 0, 0, 0)
icon.Size = UDim2.new(1, 0, 1, 0)
icon.Image = ""
icon.BackgroundTransparency = 1
icon.Parent = button
local activationButton = Instance.new("ImageLabel")
activationButton.Name = "ActivationButton"
activationButton.Position = UDim2.new(-0.3, 0, -0.4, 0)
activationButton.Size = UDim2.new(.8, 0, .8 * (PROMPT_SIZE.X / PROMPT_SIZE.Y), 0)
activationButton.Image = "rbxasset://textures/ui/Settings/Help/XButtonDark.png"
activationButton.BackgroundTransparency = 1
activationButton.Visible = false
activationButton.Parent = button
end
function getChatColor(tone)
if tone == Enum.DialogTone.Neutral then
return Enum.ChatColor.Blue
elseif tone == Enum.DialogTone.Friendly then
return Enum.ChatColor.Green
elseif tone == Enum.DialogTone.Enemy then
return Enum.ChatColor.Red
end
end
function styleChoices()
for _, obj in pairs(choices) do
obj.BackgroundTransparency = 1
end
lastChoice.BackgroundTransparency = 1
end
function styleMainFrame(tone)
if tone == Enum.DialogTone.Neutral then
mainFrame.Style = Enum.FrameStyle.ChatBlue
elseif tone == Enum.DialogTone.Friendly then
mainFrame.Style = Enum.FrameStyle.ChatGreen
elseif tone == Enum.DialogTone.Enemy then
mainFrame.Style = Enum.FrameStyle.ChatRed
end
styleChoices()
end
function setChatNotificationTone(gui, purpose, tone)
if tone == Enum.DialogTone.Neutral then
gui.Background.Image = "rbxasset://textures/ui/chatBubble_blue_notify_bkg.png"
elseif tone == Enum.DialogTone.Friendly then
gui.Background.Image = "rbxasset://textures/ui/chatBubble_green_notify_bkg.png"
elseif tone == Enum.DialogTone.Enemy then
gui.Background.Image = "rbxasset://textures/ui/chatBubble_red_notify_bkg.png"
end
local newIcon, size = unpack(PURPOSE_DATA[purpose])
local relativeSize = size / PROMPT_SIZE
gui.Background.Icon.Size = UDim2.new(relativeSize.X, 0, relativeSize.Y, 0)
gui.Background.Icon.Position = UDim2.new(0.5 - (relativeSize.X / 2), 0, 0.4 - (relativeSize.Y / 2), 0)
gui.Background.Icon.Image = newIcon
end
function createMessageDialog()
messageDialog = Instance.new("Frame");
messageDialog.Name = "DialogScriptMessage"
messageDialog.Style = Enum.FrameStyle.Custom
messageDialog.BackgroundTransparency = 0.5
messageDialog.BackgroundColor3 = Color3.new(31 / 255, 31 / 255, 31 / 255)
messageDialog.Visible = false
messageDialog.RobloxLocked = true
local text = Instance.new("TextLabel")
text.Name = "Text"
text.Position = UDim2.new(0, 0, 0, -1)
text.Size = UDim2.new(1, 0, 1, 0)
text.FontSize = Enum.FontSize.Size14
text.BackgroundTransparency = 1
text.TextColor3 = Color3.new(1, 1, 1)
text.Parent = messageDialog
end
function showMessage(msg, size)
messageDialog.Text.Text = msg
messageDialog.Size = UDim2.new(0, size, 0, 40)
messageDialog.Position = UDim2.new(0.5, -size / 2, 0.5, -40)
messageDialog.Visible = true
wait(2)
messageDialog.Visible = false
end
function variableDelay(str)
local length = math.min(string.len(str), 100)
wait(0.75 + ((length / 75) * 1.5))
end
function resetColor(frame)
frame.BackgroundTransparency = 1
end
function wanderDialog()
mainFrame.Visible = false
endDialog()
showMessage(characterWanderedOffMessage, characterWanderedOffSize)
end
function timeoutDialog()
mainFrame.Visible = false
endDialog()
showMessage(conversationTimedOut, conversationTimedOutSize)
end
function normalEndDialog()
endDialog()
end
function endDialog()
if currentDialogTimeoutCoroutine then
coroutineMap[currentDialogTimeoutCoroutine] = false
currentDialogTimeoutCoroutine = nil
end
local dialog = currentConversationDialog
currentConversationDialog = nil
if dialog and dialog.InUse then
-- Waits 5 seconds before setting InUse to false
setDialogInUseEvent:FireServer(dialog, false, 5)
delay(5, function()
dialog.InUse = false
end)
end
for dialog, gui in pairs(dialogMap) do
if dialog and gui then
gui.Enabled = not dialog.InUse
end
end
contextActionService:UnbindCoreAction("Nothing")
currentConversationPartner = nil
if touchControlGui then
touchControlGui.Visible = true
end
end
function sanitizeMessage(msg)
if string.len(msg) == 0 then
return "..."
else
return msg
end
end
local function chatFunc(dialog, ...)
if isDialogMultiplePlayers(dialog) then
game:GetService("Chat"):ChatLocal(...)
else
game:GetService("Chat"):Chat(...)
end
end
function selectChoice(choice)
renewKillswitch(currentConversationDialog)
--First hide the Gui
mainFrame.Visible = false
if choice == lastChoice then
chatFunc(currentConversationDialog, localPlayer.Character, lastChoice.UserPrompt.Text, getChatColor(currentTone()))
normalEndDialog()
else
local dialogChoice = choiceMap[choice]
chatFunc(currentConversationDialog, localPlayer.Character, sanitizeMessage(dialogChoice.UserDialog), getChatColor(currentTone()))
wait(1)
currentConversationDialog:SignalDialogChoiceSelected(localPlayer, dialogChoice)
chatFunc(currentConversationDialog, currentConversationPartner, sanitizeMessage(dialogChoice.ResponseDialog), getChatColor(currentTone()))
variableDelay(dialogChoice.ResponseDialog)
presentDialogChoices(currentConversationPartner, dialogChoice:GetChildren(), dialogChoice)
end
end
function newChoice()
local dummyFrame = Instance.new("Frame")
dummyFrame.Visible = false
local frame = Instance.new("TextButton")
frame.BackgroundColor3 = Color3.new(227 / 255, 227 / 255, 227 / 255)
frame.BackgroundTransparency = 1
frame.AutoButtonColor = false
frame.BorderSizePixel = 0
frame.Text = ""
frame.MouseEnter:connect(function()
frame.BackgroundTransparency = 0
end)
frame.MouseLeave:connect(function()
frame.BackgroundTransparency = 1
end)
frame.SelectionImageObject = dummyFrame
frame.MouseButton1Click:connect(function()
selectChoice(frame)
end)
frame.RobloxLocked = true
local prompt = Instance.new("TextLabel")
prompt.Name = "UserPrompt"
prompt.BackgroundTransparency = 1
prompt.Font = Enum.Font.SourceSans
prompt.FontSize = FONT_SIZE
prompt.Position = UDim2.new(0, 40, 0, 0)
prompt.Size = UDim2.new(1, -32 - 40, 1, 0)
prompt.TextXAlignment = Enum.TextXAlignment.Left
prompt.TextYAlignment = Enum.TextYAlignment.Center
prompt.TextWrap = true
prompt.Parent = frame
local selectionButton = Instance.new("ImageLabel")
selectionButton.Name = "RBXchatDialogSelectionButton"
selectionButton.Position = UDim2.new(0, 0, 0.5, -33 / 2)
selectionButton.Size = UDim2.new(0, 33, 0, 33)
selectionButton.Image = "rbxasset://textures/ui/Settings/Help/AButtonLightSmall.png"
selectionButton.BackgroundTransparency = 1
selectionButton.Visible = false
selectionButton.Parent = frame
return frame
end
function initialize(parent)
choices[1] = newChoice()
choices[2] = newChoice()
choices[3] = newChoice()
choices[4] = newChoice()
lastChoice = newChoice()
lastChoice.UserPrompt.Text = "Goodbye!"
lastChoice.Size = UDim2.new(1, WIDTH_BONUS, 0, TEXT_HEIGHT + CHOICE_PADDING)
mainFrame = Instance.new("Frame")
mainFrame.Name = "UserDialogArea"
mainFrame.Size = UDim2.new(0, FRAME_WIDTH, 0, 200)
mainFrame.Style = Enum.FrameStyle.ChatBlue
mainFrame.Visible = false
for n, obj in pairs(choices) do
obj.RobloxLocked = true
obj.Parent = mainFrame
end
lastChoice.RobloxLocked = true
lastChoice.Parent = mainFrame
mainFrame.RobloxLocked = true
mainFrame.Parent = parent
end
function presentDialogChoices(talkingPart, dialogChoices, parentDialog)
if not currentConversationDialog then
return
end
currentConversationPartner = talkingPart
local sortedDialogChoices = {}
for n, obj in pairs(dialogChoices) do
if obj:IsA("DialogChoice") then
table.insert(sortedDialogChoices, obj)
end
end
table.sort(sortedDialogChoices, function(a, b)
return a.Name < b.Name
end)
if #sortedDialogChoices == 0 then
normalEndDialog()
return
end
local pos = 1
local yPosition = 0
choiceMap = {}
for n, obj in pairs(choices) do
obj.Visible = false
end
for n, obj in pairs(sortedDialogChoices) do
if pos <= #choices then
--3 lines is the maximum, set it to that temporarily
choices[pos].Size = UDim2.new(1, WIDTH_BONUS, 0, TEXT_HEIGHT * 3)
if FFlagCoreScriptTranslateGameText2 then
GameTranslator:TranslateAndRegister(choices[pos].UserPrompt, obj, obj.UserDialog)
else
choices[pos].UserPrompt.Text = GameTranslator:TranslateGameText(obj, obj.UserDialog)
end
local height = (math.ceil(choices[pos].UserPrompt.TextBounds.Y / TEXT_HEIGHT) * TEXT_HEIGHT) + CHOICE_PADDING
choices[pos].Position = UDim2.new(0, XPOS_OFFSET, 0, YPOS_OFFSET + yPosition)
choices[pos].Size = UDim2.new(1, WIDTH_BONUS, 0, height)
choices[pos].Visible = true
choiceMap[choices[pos]] = obj
yPosition = yPosition + height + 1 -- The +1 makes highlights not overlap
pos = pos + 1
end
end
lastChoice.Size = UDim2.new(1, WIDTH_BONUS, 0, TEXT_HEIGHT * 3)
lastChoice.UserPrompt.Text = parentDialog.GoodbyeDialog == "" and "Goodbye!" or parentDialog.GoodbyeDialog
local height = (math.ceil(lastChoice.UserPrompt.TextBounds.Y / TEXT_HEIGHT) * TEXT_HEIGHT) + CHOICE_PADDING
lastChoice.Size = UDim2.new(1, WIDTH_BONUS, 0, height)
lastChoice.Position = UDim2.new(0, XPOS_OFFSET, 0, YPOS_OFFSET + yPosition)
lastChoice.Visible = true
if goodbyeChoiceActiveFlag and not parentDialog.GoodbyeChoiceActive then
lastChoice.Visible = false
mainFrame.Size = UDim2.new(0, FRAME_WIDTH, 0, yPosition + (STYLE_PADDING * 2) + (YPOS_OFFSET * 2))
else
mainFrame.Size = UDim2.new(0, FRAME_WIDTH, 0, yPosition + lastChoice.AbsoluteSize.Y + (STYLE_PADDING * 2) + (YPOS_OFFSET * 2))
end
mainFrame.Position = UDim2.new(0, 20, 1.0, -mainFrame.Size.Y.Offset - 20)
if isSmallTouchScreen then
local touchScreenGui = localPlayer.PlayerGui:FindFirstChild("TouchGui")
if touchScreenGui then
touchControlGui = touchScreenGui:FindFirstChild("TouchControlFrame")
if touchControlGui then
touchControlGui.Visible = false
end
end
mainFrame.Position = UDim2.new(0, 10, 1.0, -mainFrame.Size.Y.Offset)
end
styleMainFrame(currentTone())
mainFrame.Visible = true
if usingGamepad then
game:GetService("GuiService").SelectedCoreObject = choices[1]
end
end
function doDialog(dialog)
if dialog.InitialPrompt == "" then
warn("Can't start a dialog with an empty InitialPrompt")
return
end
local isMultiplePlayers = isDialogMultiplePlayers(dialog)
if dialog.InUse and not isMultiplePlayers then
return
else
currentConversationDialog = dialog
dialog.InUse = true
-- only bind if we actual enter the dialog
contextActionService:BindCoreAction("Nothing", function()
end, false, Enum.UserInputType.Gamepad1, Enum.UserInputType.Gamepad2, Enum.UserInputType.Gamepad3, Enum.UserInputType.Gamepad4)
-- Immediately sets InUse to true on the server
setDialogInUseEvent:FireServer(dialog, true, 0)
end
chatFunc(dialog, dialog.Parent, dialog.InitialPrompt, getChatColor(dialog.Tone))
variableDelay(dialog.InitialPrompt)
presentDialogChoices(dialog.Parent, dialog:GetChildren(), dialog)
end
function renewKillswitch(dialog)
if currentDialogTimeoutCoroutine then
coroutineMap[currentDialogTimeoutCoroutine] = false
currentDialogTimeoutCoroutine = nil
end
currentDialogTimeoutCoroutine = coroutine.create(function(thisCoroutine)
wait(15)
if thisCoroutine ~= nil then
if coroutineMap[thisCoroutine] == nil then
setDialogInUseEvent:FireServer(dialog, false, 0)
dialog.InUse = false
end
coroutineMap[thisCoroutine] = nil
end
end)
coroutine.resume(currentDialogTimeoutCoroutine, currentDialogTimeoutCoroutine)
end
function checkForLeaveArea()
while currentConversationDialog do
if currentConversationDialog.Parent and (localPlayer:DistanceFromCharacter(currentConversationDialog.Parent.Position) >= currentConversationDialog.ConversationDistance) then
wanderDialog()
end
wait(1)
end
end
function startDialog(dialog)
if dialog.Parent and dialog.Parent:IsA("BasePart") then
if FFlagWeDontWantAnyGoogleAnalyticsHerePlease then
AnalyticsService:TrackEvent("Dialogue", "Old Dialogue", "Conversation Initiated")
else
game:ReportInGoogleAnalytics("Dialogue", "Old Dialogue", "Conversation Initiated")
end
if localPlayer:DistanceFromCharacter(dialog.Parent.Position) >= dialog.ConversationDistance then
showMessage(tooFarAwayMessage, tooFarAwaySize)
return
end
for dialog, gui in pairs(dialogMap) do
if dialog and gui then
gui.Enabled = false
end
end
renewKillswitch(dialog)
delay(1, checkForLeaveArea)
doDialog(dialog)
end
end
function removeDialog(dialog)
if dialogMap[dialog] then
dialogMap[dialog]:Destroy()
dialogMap[dialog] = nil
end
if dialogConnections[dialog] then
dialogConnections[dialog]:disconnect()
dialogConnections[dialog] = nil
end
end
function addDialog(dialog)
if dialog.Parent then
if dialog.Parent:IsA("BasePart") and dialog:IsDescendantOf(game.Workspace) then
FlagHasReportedPlace = true
if FFlagWeDontWantAnyGoogleAnalyticsHerePlease then
AnalyticsService:TrackEvent("Dialogue", "Old Dialogue", "Used In Place", nil, game.PlaceId)
else
game:ReportInGoogleAnalytics("Dialogue", "Old Dialogue", "Used In Place", nil, game.PlaceId)
end
local chatGui = chatNotificationGui:clone()
chatGui.Adornee = dialog.Parent
chatGui.RobloxLocked = true
chatGui.Enabled = not dialog.InUse or isDialogMultiplePlayers(dialog)
chatGui.Parent = CoreGui
chatGui.Background.MouseButton1Click:connect(function()
startDialog(dialog)
end)
setChatNotificationTone(chatGui, dialog.Purpose, dialog.Tone)
dialogMap[dialog] = chatGui
dialogConnections[dialog] = dialog.Changed:connect(function(prop)
if prop == "Parent" and dialog.Parent then
--This handles the reparenting case, seperate from removal case
removeDialog(dialog)
addDialog(dialog)
elseif prop == "InUse" then
if not isDialogMultiplePlayers(dialog) then
chatGui.Enabled = (currentConversationDialog == nil) and not dialog.InUse
else
chatGui.Enabled = (currentConversationDialog ~= dialog)
end
if not dialog.InUse and not isDialogMultiplePlayers(player) and dialog == currentConversationDialog then
timeoutDialog()
end
elseif prop == "Tone" or prop == "Purpose" then
setChatNotificationTone(chatGui, dialog.Purpose, dialog.Tone)
end
end)
else -- still need to listen to parent changes even if current parent is not a BasePart
dialogConnections[dialog] = dialog.Changed:connect(function(prop)
if prop == "Parent" and dialog.Parent then
--This handles the reparenting case, seperate from removal case
removeDialog(dialog)
addDialog(dialog)
end
end)
end
end
end
function onLoad()
waitForProperty(localPlayer, "Character")
createChatNotificationGui()
createMessageDialog()
messageDialog.RobloxLocked = true
messageDialog.Parent = gui
gui:WaitForChild("BottomLeftControl")
local frame = Instance.new("Frame")
frame.Name = "DialogFrame"
frame.Position = UDim2.new(0, 0, 0, 0)
frame.Size = UDim2.new(0, 0, 0, 0)
frame.BackgroundTransparency = 1
frame.RobloxLocked = true
game:GetService("GuiService"):AddSelectionParent("RBXDialogGroup", frame)
if (touchEnabled and not isSmallTouchScreen) then
frame.Position = UDim2.new(0, 20, 0.5, 0)
frame.Size = UDim2.new(0.25, 0, 0.1, 0)
frame.Parent = gui
elseif isSmallTouchScreen then
frame.Position = UDim2.new(0, 0, .9, -10)
frame.Size = UDim2.new(0.25, 0, 0.1, 0)
frame.Parent = gui
else
frame.Parent = gui.BottomLeftControl
end
initialize(frame)
game:GetService("CollectionService").ItemAdded:connect(function(obj)
if obj:IsA("Dialog") then
addDialog(obj)
end
end)
game:GetService("CollectionService").ItemRemoved:connect(function(obj)
if obj:IsA("Dialog") then
removeDialog(obj)
end
end)
for i, obj in pairs(game:GetService("CollectionService"):GetCollection("Dialog")) do
if obj:IsA("Dialog") then
addDialog(obj)
end
end
end
function getLocalHumanoidRootPart()
if localPlayer.Character then
return localPlayer.Character:FindFirstChild("HumanoidRootPart")
end
end
function dialogIsValid(dialog)
return dialog and dialog.Parent and dialog.Parent:IsA("BasePart")
end
local lastClosestDialog = nil
local getClosestDialogToPosition = guiService.GetClosestDialogToPosition
game:GetService("RunService").Heartbeat:connect(function()
local closestDistance = math.huge
local closestDialog = nil
local humanoidRootPart = getLocalHumanoidRootPart()
if humanoidRootPart then
local characterPosition = humanoidRootPart.Position
closestDialog = getClosestDialogToPosition(guiService, characterPosition)
end
if getLocalHumanoidRootPart() and dialogIsValid(closestDialog) and currentConversationDialog == nil then
local dialogTriggerDistance = closestDialog.TriggerDistance
local dialogTriggerOffset = closestDialog.TriggerOffset
local distanceFromCharacterWithOffset = localPlayer:DistanceFromCharacter(
closestDialog.Parent.Position + dialogTriggerOffset
)
if dialogTriggerDistance ~= 0 and
distanceFromCharacterWithOffset < closestDialog.ConversationDistance and
distanceFromCharacterWithOffset < dialogTriggerDistance then
startDialog(closestDialog)
end
end
if usingGamepad == true then
if closestDialog ~= lastClosestDialog then
if dialogMap[lastClosestDialog] then
dialogMap[lastClosestDialog].Background.ActivationButton.Visible = false
end
lastClosestDialog = closestDialog
contextActionService:UnbindCoreAction("StartDialogAction")
if closestDialog ~= nil then
contextActionService:BindCoreAction("StartDialogAction", function(actionName, userInputState, inputObject)
if userInputState == Enum.UserInputState.Begin then
if closestDialog and closestDialog.Parent then
startDialog(closestDialog)
end
end
end, false, Enum.KeyCode.ButtonX)
if dialogMap[closestDialog] then
dialogMap[closestDialog].Background.ActivationButton.Visible = true
end
end -- closestDialog ~= nil
end -- closestDialog ~= lastClosestDialog
end -- usingGamepad == true
end)
local lastSelectedChoice = nil
guiService.Changed:connect(function(property)
if property == "SelectedCoreObject" then
if lastSelectedChoice and lastSelectedChoice:FindFirstChild("RBXchatDialogSelectionButton") then
lastSelectedChoice:FindFirstChild("RBXchatDialogSelectionButton").Visible = false
lastSelectedChoice.BackgroundTransparency = 1
end
lastSelectedChoice = guiService.SelectedCoreObject
if lastSelectedChoice and lastSelectedChoice:FindFirstChild("RBXchatDialogSelectionButton") then
lastSelectedChoice:FindFirstChild("RBXchatDialogSelectionButton").Visible = true
lastSelectedChoice.BackgroundTransparency = 0
end
end
end)
onLoad()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,239 @@
--[[
Filename: PerformanceStatsManagerScript.lua
Written by: dbanks
Description: Handles performance stats gui.
--]]
--[[ Services ]]--
local PlayersService = game:GetService("Players")
local Settings = UserSettings()
local GameSettings = Settings.GameSettings
local CoreGuiService = game:GetService('CoreGui')
local AnalyticsService = game:GetService("AnalyticsService")
--[[ Modules ]]--
local GoogleAnalyticsUtils = require(CoreGuiService.RobloxGui.Modules.GoogleAnalyticsUtils)
local StatsAggregatorManagerClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsAggregatorManager)
local StatsButtonClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsButton)
local StatsUtils = require(CoreGuiService.RobloxGui.Modules.Stats.StatsUtils)
local StatsViewerClass = require(CoreGuiService.RobloxGui.Modules.Stats.StatsViewer)
local TopbarConstants = require(CoreGuiService.RobloxGui.Modules.TopbarConstants)
--[[ Script Variables ]]--
local masterFrame = Instance.new("Frame")
masterFrame.Name = "PerformanceStats"
local FFlagStatUtilsUseFormatByKey = settings():GetFFlag('StatUtilsUseFormatByKey')
local FFlagWeDontWantAnyGoogleAnalyticsHerePlease = settings():GetFFlag("WeDontWantAnyGoogleAnalyticsHerePlease")
local statsAggregatorManager = StatsAggregatorManagerClass.getSingleton()
local statsViewer = StatsViewerClass.new()
local statsButtonsByType ={}
local currentStatType = nil
for i, statType in ipairs(StatsUtils.AllStatTypes) do
local button = StatsButtonClass.new(statType)
statsButtonsByType[statType] = button
end
function ShowMasterFrame()
masterFrame.Visible = true
masterFrame.Parent = CoreGuiService.RobloxGui
end
function HideMasterFrame()
masterFrame.Visible = false
masterFrame.Parent = nil
end
--[[ Functions ]]--
function ConfigureMasterFrame()
-- Set up the main frame that contains the whole PS GUI.
-- Avoid the top button bar.
masterFrame.Position = UDim2.new(0, 0, 0, 0)
masterFrame.Size = UDim2.new(1, 0, 1, 0)
masterFrame.Selectable = false
masterFrame.BackgroundTransparency = 1.0
masterFrame.Active = false
masterFrame.ZIndex = 0
if FFlagStatUtilsUseFormatByKey then
HideMasterFrame()
end
-- FIXME(dbanks)
-- Debug, can see the whole frame.
-- masterFrame.BackgroundColor3 = Color3.new(0, 0.5, 0.5)
-- masterFrame.BackgroundTransparency = 0.8
end
function ConfigureStatButtonsInMasterFrame()
-- Set up the row of buttons across the top and handler for button press.
for i, statType in ipairs(StatsUtils.AllStatTypes) do
AddButton(statType, i)
end
end
function OnButtonToggled(toggledStatType)
local toggledButton = statsButtonsByType[toggledStatType]
local selectedState = toggledButton._isSelected
selectedState = not selectedState
if (selectedState) then
currentStatType = toggledStatType
else
currentStatType = nil
end
UpdateButtonSelectedStates()
UpdateViewerVisibility()
end
function UpdateButtonSelectedStates()
for i, buttonType in ipairs(StatsUtils.AllStatTypes) do
local button = statsButtonsByType[buttonType]
button:SetIsSelected(buttonType == currentStatType)
end
end
function UpdateViewerVisibility()
-- If a particular button/tab is on, show the Viewer.
--
-- Don't bother if we're already there.
if (currentStatType == nil) then
if statsViewer:GetIsVisible() then
statsViewer:SetVisible(false)
statsViewer:SetStatsAggregator(nil)
end
else
local somethingChanged = false
if not statsViewer:GetIsVisible() then
somethingChanged = true
statsViewer:SetVisible(true)
end
if currentStatType ~= statsViewer:GetStatType() then
statsViewer:SetStatType(currentStatType)
statsViewer:SetStatsAggregator(statsAggregatorManager:GetAggregator(currentStatType))
somethingChanged = true
end
if somethingChanged then
-- track it.
if FFlagWeDontWantAnyGoogleAnalyticsHerePlease then
AnalyticsService:TrackEvent("Game", "Enlarge PerfStat", currentStatType, 0)
else
game:ReportInGoogleAnalytics(GoogleAnalyticsUtils.CA_CATEGORY_GAME,
"Enlarge PerfStat",
currentStatType,
0)
end
end
end
end
function AddButton(statType, index)
-- Configure size and position of button.
-- Configure callback behavior to toggle
-- button on or off and show/hide viewer.
-- Parent button in main screen.
local button = statsButtonsByType[statType]
button:SetParent(masterFrame)
button:SetStatsAggregator(
statsAggregatorManager:GetAggregator(statType))
local fraction = 1.0/StatsUtils.NumButtonTypes
local size = UDim2.new(fraction, 0, 0, StatsUtils.ButtonHeight)
local position = UDim2.new(fraction * (index - 1), 0, 0, 0)
button:SetSizeAndPosition(size, position)
button:SetToggleCallbackFunction(OnButtonToggled)
end
function ConfigureStatViewerInMasterFrame()
-- Set up the widget that shows currently selected button.
statsViewer:SetParent(masterFrame)
local size = UDim2.new(0, StatsUtils.ViewerWidth, 0, StatsUtils.ViewerHeight)
local position = UDim2.new(1, -StatsUtils.ViewerWidth,
0, StatsUtils.ButtonHeight + StatsUtils.ViewerTopMargin)
statsViewer:SetSizeAndPosition(size, position)
end
function UpdatePerformanceStatsVisibility()
local shouldBeVisible = StatsUtils.PerformanceStatsShouldBeVisible()
if (shouldBeVisible == masterFrame.Visible) then
return
end
if FFlagStatUtilsUseFormatByKey then
if shouldBeVisible then
ShowMasterFrame()
else
HideMasterFrame()
end
else
if shouldBeVisible then
masterFrame.Visible = true
masterFrame.Parent = CoreGuiService.RobloxGui
else
masterFrame.Visible = false
masterFrame.Parent = nil
end
end
-- Let the children respond to the transition that they are/are not visible.
statsViewer:OnPerformanceStatsShouldBeVisibleChanged()
for i, buttonType in ipairs(StatsUtils.AllStatTypes) do
local button = statsButtonsByType[buttonType]
button:OnPerformanceStatsShouldBeVisibleChanged()
end
-- track it.
local actionName = "Hide PerfStats"
if shouldBeVisible then
actionName = "Show PerfStats"
end
if FFlagWeDontWantAnyGoogleAnalyticsHerePlease then
AnalyticsService:TrackEvent("Game", actionName, "", 0)
else
game:ReportInGoogleAnalytics(GoogleAnalyticsUtils.CA_CATEGORY_GAME,
actionName,
"",
0)
end
end
--[[ Top Level Code ]]--
-- Set up our GUI.
ConfigureMasterFrame()
ConfigureStatButtonsInMasterFrame()
ConfigureStatViewerInMasterFrame()
-- Watch for changes in performance stats visibility.
GameSettings.PerformanceStatsVisibleChanged:connect(
UpdatePerformanceStatsVisibility)
-- Make sure we're showing buttons and viewer based on current selection.
UpdateButtonSelectedStates()
UpdateViewerVisibility()
-- Make sure stats are visible or not, as specified by current setting.
UpdatePerformanceStatsVisibility()
-- This may change if Player shows up...
spawn(function()
local localPlayer = PlayersService.LocalPlayer
while not localPlayer do
PlayersService.PlayerAdded:wait()
localPlayer = PlayersService.LocalPlayer
end
UpdatePerformanceStatsVisibility()
end)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
--[[
// Filename: VehicleHud.lua
// Version 1.0
// Written by: jmargh
// Description: Implementation of the VehicleSeat HUD
// TODO:
Once this is live and stable, move to PlayerScripts as module
]]
local RunService = game:GetService('RunService')
local Players = game:GetService('Players')
while not Players.LocalPlayer do
wait()
end
local LocalPlayer = Players.LocalPlayer
local RobloxGui = script.Parent
local CurrentVehicleSeat = nil
local VehicleSeatHeartbeatCn = nil
local VehicleSeatHUDChangedCn = nil
local RobloxGui = game:GetService("CoreGui"):WaitForChild("RobloxGui")
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
--[[ Images ]]--
local VEHICLE_HUD_BG = 'rbxasset://textures/ui/Vehicle/SpeedBarBKG.png'
local SPEED_BAR_EMPTY = 'rbxasset://textures/ui/Vehicle/SpeedBarEmpty.png'
local SPEED_BAR = 'rbxasset://textures/ui/Vehicle/SpeedBar.png'
--[[ Constants ]]--
local BOTTOM_OFFSET = (isTenFootInterface and 100 or 70)
--[[ Gui Creation ]]--
local function createImageLabel(name, size, position, image, parent)
local imageLabel = Instance.new('ImageLabel')
imageLabel.Name = name
imageLabel.Size = size
imageLabel.Position = position
imageLabel.BackgroundTransparency = 1
imageLabel.Image = image
imageLabel.Parent = parent
return imageLabel
end
local function createTextLabel(name, alignment, text, parent)
local textLabel = Instance.new('TextLabel')
textLabel.Name = name
textLabel.Size = UDim2.new(1, -4, 0, (isTenFootInterface and 50 or 20))
textLabel.Position = UDim2.new(0, 2, 0, (isTenFootInterface and -50 or -20))
textLabel.BackgroundTransparency = 1
textLabel.TextXAlignment = alignment
textLabel.Font = Enum.Font.SourceSans
textLabel.FontSize = (isTenFootInterface and Enum.FontSize.Size48 or Enum.FontSize.Size18)
textLabel.TextColor3 = Color3.new(1, 1, 1)
textLabel.TextStrokeTransparency = 0.5
textLabel.TextStrokeColor3 = Color3.new(49/255, 49/255, 49/255)
textLabel.Text = text
textLabel.Parent = parent
return textLabel
end
local VehicleHudFrame = Instance.new('Frame')
VehicleHudFrame.Name = "VehicleHudFrame"
VehicleHudFrame.Size = UDim2.new(0, (isTenFootInterface and 316 or 158), 0, (isTenFootInterface and 50 or 14))
VehicleHudFrame.Position = UDim2.new(0.5, -(VehicleHudFrame.Size.X.Offset/2), 1, -BOTTOM_OFFSET - VehicleHudFrame.Size.Y.Offset)
VehicleHudFrame.BackgroundTransparency = 1
VehicleHudFrame.Visible = false
VehicleHudFrame.Parent = RobloxGui
local speedBarClippingFrame = Instance.new("Frame")
speedBarClippingFrame.Name = "SpeedBarClippingFrame"
speedBarClippingFrame.Size = UDim2.new(0, 0, 0, (isTenFootInterface and 24 or 4))
speedBarClippingFrame.Position = UDim2.new(0.5, (isTenFootInterface and -142 or -71), 0.5, (isTenFootInterface and -13 or -2))
speedBarClippingFrame.BackgroundTransparency = 1
speedBarClippingFrame.ClipsDescendants = true
speedBarClippingFrame.Parent = VehicleHudFrame
local HudBG = createImageLabel("HudBG", UDim2.new(1, 0, 1, 0), UDim2.new(0, 0, 0, 1), VEHICLE_HUD_BG, VehicleHudFrame)
local SpeedBG = createImageLabel("SpeedBG", UDim2.new(0, (isTenFootInterface and 284 or 142), 0, (isTenFootInterface and 24 or 4)), UDim2.new(0.5, (isTenFootInterface and -142 or -71), 0.5, (isTenFootInterface and -13 or -2)), SPEED_BAR_EMPTY, VehicleHudFrame)
local SpeedBarImage = createImageLabel("SpeedBarImage", UDim2.new(0, (isTenFootInterface and 284 or 142), 1, 0), UDim2.new(0, 0, 0, 0), SPEED_BAR, speedBarClippingFrame)
SpeedBarImage.ZIndex = 2
local SpeedLabel = createTextLabel("SpeedLabel", Enum.TextXAlignment.Left, "Speed", VehicleHudFrame)
local SpeedText = createTextLabel("SpeedText", Enum.TextXAlignment.Right, "0", VehicleHudFrame)
--[[ Local Functions ]]--
local function getHumanoid()
local character = LocalPlayer and LocalPlayer.Character
if character then
for _,child in pairs(character:GetChildren()) do
if child:IsA('Humanoid') then
return child
end
end
end
end
local function onHeartbeat()
if CurrentVehicleSeat then
local speed = CurrentVehicleSeat.Velocity.magnitude
SpeedText.Text = tostring(math.min(math.floor(speed), 9999))
local drawSize = math.floor((speed / CurrentVehicleSeat.MaxSpeed) * SpeedBG.Size.X.Offset)
drawSize = math.min(drawSize, SpeedBG.Size.X.Offset)
speedBarClippingFrame.Size = UDim2.new(0, drawSize, 0, (isTenFootInterface and 24 or 4))
end
end
local function onVehicleSeatChanged(property)
if property == "HeadsUpDisplay" then
VehicleHudFrame.Visible = not VehicleHudFrame.Visible
end
end
local function onSeated(active, currentSeatPart)
if active then
if currentSeatPart and currentSeatPart:IsA('VehicleSeat') then
CurrentVehicleSeat = currentSeatPart
VehicleHudFrame.Visible = CurrentVehicleSeat.HeadsUpDisplay
VehicleSeatHeartbeatCn = RunService.Heartbeat:connect(onHeartbeat)
VehicleSeatHUDChangedCn = CurrentVehicleSeat.Changed:connect(onVehicleSeatChanged)
end
else
if CurrentVehicleSeat then
VehicleHudFrame.Visible = false
CurrentVehicleSeat = nil
if VehicleSeatHeartbeatCn then
VehicleSeatHeartbeatCn:disconnect()
VehicleSeatHeartbeatCn = nil
end
if VehicleSeatHUDChangedCn then
VehicleSeatHUDChangedCn:disconnect()
VehicleSeatHUDChangedCn = nil
end
end
end
end
local function connectSeated()
local humanoid = getHumanoid()
while not humanoid do
wait()
humanoid = getHumanoid()
end
humanoid.Seated:connect(onSeated)
end
if LocalPlayer.Character then
connectSeated()
end
LocalPlayer.CharacterAdded:connect(function(character)
onSeated(false)
connectSeated()
end)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,964 @@
-- Creates the generic "ROBLOX" loading screen on startup
-- Written by ArceusInator & Ben Tkacheff, 2014
-- Updates by 0xBAADF00D, 2017
local AssetService = game:GetService('AssetService')
local MarketplaceService = game:GetService("MarketplaceService")
local UserInputService = game:GetService("UserInputService")
local VRService = game:GetService("VRService")
local GuiService = game:GetService("GuiService")
local ContextActionService = game:GetService("ContextActionService")
local RunService = game:GetService("RunService")
local ContentProvider = game:GetService("ContentProvider")
local RobloxGui = game:GetService("CoreGui"):WaitForChild("RobloxGui")
--FFlags
local FFlagLoadTheLoadingScreenFasterSuccess, FFlagLoadTheLoadingScreenFasterValue = pcall(function() return settings():GetFFlag("LoadTheLoadingScreenFaster") end)
local FFlagLoadTheLoadingScreenFaster = FFlagLoadTheLoadingScreenFasterSuccess and FFlagLoadTheLoadingScreenFasterValue
local FFlagFixLoadingScreenJankiness = settings():GetFFlag("FixLoadingScreenJankiness")
local FFlagLoadingScreenUseLocalizationTable = settings():GetFFlag("LoadingScreenUseLocalizationTable")
local FFlagShowConnectionErrorCode = settings():GetFFlag("ShowConnectionErrorCode")
local FFlagConnectionScriptEnabled = settings():GetFFlag("ConnectionScriptEnabled")
local debugMode = false
local startTime = tick()
local loadingImageInputBeganConn = nil
local COLORS = {
BACKGROUND_COLOR = Color3.fromRGB(45, 45, 45),
TEXT_COLOR = Color3.fromRGB(255, 255, 255),
ERROR = Color3.fromRGB(253, 68, 72)
}
local spinnerImageId = "rbxasset://textures/loading/robloxTilt.png"
local gameIconSubstitutionType = {
None = 0;
Unapproved = 1;
PendingReview = 2;
Broken = 3;
Unavailable = 4;
Unknown = 5;
}
--
-- Variables
local GameAssetInfo -- loaded by InfoProvider:LoadAssets()
local currScreenGui, renderSteppedConnection = nil, nil
local destroyingBackground, destroyedLoadingGui, hasReplicatedFirstElements = false, false, false
local isTenFootInterface = GuiService:IsTenFootInterface()
local platform = UserInputService:GetPlatform()
local placeLabel, creatorLabel = nil, nil
local backgroundFadeStarted = false
local tweenPlaceIcon = nil
local layoutIsReady = false
local connectionHealthShown = false
local connectionHealthCon
local function IsConvertMyPlaceNameInXboxAppEnabled()
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
local success, flagValue = pcall(function() return settings():GetFFlag("ConvertMyPlaceNameInXboxApp") end)
return (success and flagValue == true)
end
return false
end
--
-- Utility functions
local create = function(className, defaultParent)
return function(propertyList)
local object = Instance.new(className)
local parent = nil
for index, value in next, propertyList do
if typeof(index) == 'string' then
if index == 'Parent' then
parent = value
else
object[index] = value
end
else
local valueType = typeof(value)
if valueType == 'function' then
value(object)
elseif valueType == 'Instance' then
value.Parent = object
end
end
end
if parent then
object.Parent = parent
end
if object.Parent == nil then
object.Parent = defaultParent
end
return object
end
end
--
-- Create objects
local MainGui = {}
local InfoProvider = {}
local function WaitForPlaceId()
local placeId = game.PlaceId
if placeId == 0 then
game:GetPropertyChangedSignal("PlaceId"):wait()
placeId = game.PlaceId
end
return placeId
end
local function ExtractGeneratedUsername(gameName)
local tempUsername = string.match(gameName, "^([0-9a-fA-F]+)'s Place$")
if tempUsername and #tempUsername == 32 then
return tempUsername
end
end
-- Fix places that have been made with incorrect temporary usernames
local function GetFilteredGameName(gameName, creatorName)
if gameName and type(gameName) == 'string' then
local tempUsername = ExtractGeneratedUsername(gameName)
if tempUsername then
local newGameName = string.gsub(gameName, tempUsername, creatorName, 1)
if newGameName then
return newGameName
end
end
end
return gameName
end
function InfoProvider:GetGameName()
if GameAssetInfo ~= nil then
if IsConvertMyPlaceNameInXboxAppEnabled() then
return GetFilteredGameName(GameAssetInfo.Name, self:GetCreatorName())
else
return GameAssetInfo.Name
end
else
return ''
end
end
function InfoProvider:GetCreatorName()
if GameAssetInfo ~= nil then
return GameAssetInfo.Creator.Name
else
return ''
end
end
function InfoProvider:LoadAssets()
if FFlagLoadTheLoadingScreenFaster then
coroutine.wrap(function()
local placeId = WaitForPlaceId()
local success, result = pcall(function()
GameAssetInfo = MarketplaceService:GetProductInfo(placeId)
end)
if not success then
print("LoadingScript->InfoProvider:LoadAssets:", result)
end
end)()
else
--spawn() == slowpoke.jpg
spawn(function()
local PLACEID = game.PlaceId
if PLACEID <= 0 then
while game.PlaceId <= 0 do
wait()
end
PLACEID = game.PlaceId
end
-- load game asset info
coroutine.resume(coroutine.create(function()
local success, result = pcall(function()
GameAssetInfo = MarketplaceService:GetProductInfo(PLACEID)
end)
if not success then
print("LoadingScript->InfoProvider:LoadAssets:", result)
end
end))
end)
end
end
-- create a cancel binding for console to be able to cancel anytime while loading
local function createTenfootCancelGui()
local cancelLabel = create'ImageLabel'
{
Name = "CancelLabel",
Size = UDim2.new(0, 83, 0, 83),
Position = UDim2.new(1, -32 - 83, 0, 32),
BackgroundTransparency = 1,
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/BButton.png'
}
local cancelText = create'TextLabel'
{
Name = "CancelText",
Size = UDim2.new(0, 400, 0, 83),
Position = UDim2.new(1, -131, 0, 32),
AnchorPoint = Vector2.new(1, 0),
BackgroundTransparency = 1,
Font = Enum.Font.SourceSans,
TextSize = 48,
TextXAlignment = Enum.TextXAlignment.Right,
TextColor3 = COLORS.TEXT_COLOR,
Text = "Cancel"
}
if not game:GetService("ReplicatedFirst"):IsFinishedReplicating() then
local seenBButtonBegin = false
ContextActionService:BindCoreAction("CancelGameLoad",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenBButtonBegin = true
elseif inputState == Enum.UserInputState.End and seenBButtonBegin then
cancelLabel:Destroy()
cancelText.Text = "Canceling..."
cancelText.Position = UDim2.new(1, -32, 0, 32)
ContextActionService:UnbindCoreAction('CancelGameLoad')
game:Shutdown()
end
end,
false,
Enum.KeyCode.ButtonB)
end
while cancelLabel.Parent == nil do
if currScreenGui then
local blackFrame = currScreenGui:FindFirstChild('BlackFrame')
if blackFrame then
cancelLabel.Parent = blackFrame
cancelText.Parent = blackFrame
break
end
end
wait()
end
end
--
-- Declare member functions
function MainGui:GenerateMain()
local screenGui = create 'ScreenGui' {
Name = 'RobloxLoadingGui'
}
--
-- create descendant frames
local mainBackgroundContainer
local inGameGlobalGuiInset = settings():GetFVariable("InGameGlobalGuiInset")
mainBackgroundContainer = create 'Frame' {
Name = 'BlackFrame',
BackgroundColor3 = COLORS.BACKGROUND_COLOR,
BackgroundTransparency = 0,
Size = UDim2.new(1, 0, 1, inGameGlobalGuiInset),
Position = UDim2.new(0, 0, 0, -inGameGlobalGuiInset),
Active = true,
Parent = screenGui
}
local closeButton = create 'ImageButton' {
Name = 'CloseButton',
Image = 'rbxasset://textures/loading/cancelButton.png',
ImageTransparency = 1,
BackgroundTransparency = 1,
Position = UDim2.new(1, -37, 0, 5),
Size = UDim2.new(0, 32, 0, 32),
Active = false,
ZIndex = 10,
Parent = mainBackgroundContainer
}
closeButton.MouseButton1Click:connect(function()
game:Shutdown()
end)
local graphicsFrame = create 'Frame' {
Name = 'GraphicsFrame',
BorderSizePixel = 0,
BackgroundTransparency = 1,
AnchorPoint = Vector2.new(1, 1),
Position = UDim2.new(0.95, 0, 0.95, 0),
Size = UDim2.new(0.15, 0, 0.15, 0),
ZIndex = 2,
Parent = mainBackgroundContainer,
create("UIAspectRatioConstraint") {
AspectRatio = 1
},
create("UISizeConstraint") {
MaxSize = Vector2.new(100, 100)
}
}
local loadingImage = create 'ImageLabel' {
Name = 'LoadingImage',
BackgroundTransparency = 1,
Image = spinnerImageId,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(1, 0, 1, 0),
ZIndex = 2,
Parent = graphicsFrame,
}
local numberOfTaps = 0
local lastTapTime = math.huge
local doubleTapTimeThreshold = 0.5
loadingImageInputBeganConn = loadingImage.InputBegan:connect(function()
if numberOfTaps == 0 then
numberOfTaps = 1
lastTapTime = tick()
return
end
if UserInputService.TouchEnabled == true and UserInputService.MouseEnabled == false then
if tick() - lastTapTime <= doubleTapTimeThreshold then
GuiService:ShowStatsBasedOnInputString("ConnectionHealth")
connectionHealthShown = not connectionHealthShown
end
end
numberOfTaps = 0
lastTapTime = math.huge
end)
local infoFrame = create 'Frame' {
Name = 'InfoFrame',
BackgroundTransparency = 1,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(0.75, 0, 1, 0),
ZIndex = 2,
Parent = mainBackgroundContainer,
create 'UIPadding' {
Name = 'UiMessagePadding',
PaddingBottom = UDim.new(0, 25),
} or nil
}
local uiMessageFrame = create 'Frame' {
Name = 'UiMessageFrame',
BackgroundTransparency = 1,
Position = UDim2.new(0.25, 0, 1, -120),
Size = UDim2.new(1, 0, 0, 35),
ZIndex = 2,
LayoutOrder = 5,
Parent = infoFrame,
create 'TextLabel' {
Name = 'UiMessage',
BackgroundTransparency = 1,
Position = UDim2.new(0, 0, 0, 5),
Size = UDim2.new(1, 0, 0, 25),
Font = Enum.Font.SourceSansLight,
FontSize = Enum.FontSize.Size18,
TextScaled = true,
TextWrapped = true,
TextColor3 = COLORS.TEXT_COLOR,
Text = "",
TextTransparency = 1,
ZIndex = 2,
}
}
local infoFrameAspect = create("UIAspectRatioConstraint") {
AspectRatio = 3 / 2,
Parent = infoFrame
}
local infoFrameList = create("UIListLayout") {
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Vertical,
VerticalAlignment = Enum.VerticalAlignment.Center,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
Padding = UDim.new(0.05, 0),
Parent = infoFrame
}
local textContainer = create("Frame") {
BackgroundTransparency = 1,
Size = UDim2.new(2/3, 0, 1, 0),
LayoutOrder = 2,
Parent = nil,
create("UIListLayout") {
FillDirection = Enum.FillDirection.Vertical,
VerticalAlignment = Enum.VerticalAlignment.Center,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
SortOrder = Enum.SortOrder.LayoutOrder
}
}
local placeIcon = create("ImageLabel") {
Name = "PlaceIcon",
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0.5, 0, 0, 0),
AnchorPoint = Vector2.new(0.5, 0),
LayoutOrder = 1,
Parent = infoFrame,
ImageTransparency = 1,
Image = "",
create("UIAspectRatioConstraint") {
AspectRatio = 576 / 324,
AspectType = Enum.AspectType.ScaleWithParentSize,
DominantAxis = Enum.DominantAxis.Width
},
create("UISizeConstraint") {
MaxSize = Vector2.new(400, 400)
}
}
--Start trying to load the place icon image
--Web might not have this icon size generated, so we can poll asset-thumbnail/json and check
--the JSON result for thumbnailFinal/Final to see when it's done being generated so we never
--show a N/A image. This is how the console AppShell does it!
coroutine.wrap(function()
local httpService = game:GetService("HttpService")
local placeId = WaitForPlaceId()
local function tryGetFinalAsync()
local imageUrl = nil
local isGenerated = false
local success, msg = pcall(function()
imageUrl, isGenerated = AssetService:GetAssetThumbnailAsync(placeId, Vector2.new(576, 324), 1)
end)
if success and isGenerated == true and imageUrl then
ContentProvider:PreloadAsync { imageUrl }
placeIcon.Image = imageUrl
if not backgroundFadeStarted then
placeIcon.ImageTransparency = 0
end
return true
end
return false
end
while not tryGetFinalAsync() do end
end)()
placeLabel = create 'TextLabel' {
Name = 'PlaceLabel',
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 80),
Position = UDim2.new(0, 0, 0, 0),
Font = Enum.Font.SourceSans,
FontSize = (isTenFootInterface and Enum.FontSize.Size48 or Enum.FontSize.Size24),
TextWrapped = true,
TextScaled = true,
TextColor3 = COLORS.TEXT_COLOR,
TextStrokeTransparency = 1,
TextTransparency = FFlagFixLoadingScreenJankiness and 1 or nil, --setting to nil means it's not in the table at all, so it uses the default value to ensure behavior is the same. It should be 0 either way.
Text = "",
TextXAlignment = Enum.TextXAlignment.Center,
TextYAlignment = Enum.TextYAlignment.Bottom,
ZIndex = 2,
LayoutOrder = 2,
Parent = infoFrame
}
local creatorContainer = create 'Frame' {
Name = "Creator",
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 48),
LayoutOrder = 3,
create 'UIListLayout' {
FillDirection = Enum.FillDirection.Horizontal,
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 5)
}
}
local byLabel, creatorIcon = nil, nil
if isTenFootInterface then
byLabel = create'TextLabel' {
Name = "ByLabel",
BackgroundTransparency = 1,
Size = UDim2.new(0, 36, 0, 30),
Position = UDim2.new(0, 0, 0, 80),
Font = Enum.Font.SourceSansLight,
FontSize = Enum.FontSize.Size36,
TextScaled = true,
TextColor3 = COLORS.TEXT_COLOR,
TextStrokeTransparency = 1,
Text = "By",
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
ZIndex = 2,
Visible = true,
Parent = infoFrame,
LayoutOrder = 1
}
creatorIcon = create'ImageLabel' {
Name = "CreatorIcon",
BackgroundTransparency = 1,
Size = UDim2.new(0, 30, 0, 30),
Position = UDim2.new(0, 38, 0, 80),
ImageTransparency = 0,
Image = 'rbxasset://textures/ui/Shell/Icons/RobloxIcon24.png',
ZIndex = 2,
Visible = true,
Parent = infoFrame,
LayoutOrder = 2
}
end
creatorLabel = create 'TextLabel' {
Name = 'CreatorLabel',
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 30),
Position = UDim2.new(0, 0, 0, 80),
Font = Enum.Font.SourceSansLight,
FontSize = (isTenFootInterface and Enum.FontSize.Size36 or Enum.FontSize.Size18),
TextWrapped = true,
TextScaled = true,
TextColor3 = COLORS.TEXT_COLOR,
TextStrokeTransparency = 1,
Text = "",
TextXAlignment = Enum.TextXAlignment.Center,
TextYAlignment = Enum.TextYAlignment.Center,
ZIndex = 2,
LayoutOrder = 4,
Parent = infoFrame
}
if isTenFootInterface then
creatorContainer.Parent = infoFrame
byLabel.TextScaled = false
byLabel.Parent = creatorContainer
byLabel.TextXAlignment = Enum.TextXAlignment.Center
byLabel.TextYAlignment = Enum.TextYAlignment.Center
creatorIcon.Parent = creatorContainer
creatorLabel.Parent = creatorContainer
creatorLabel.TextScaled = false
creatorLabel.Position = UDim2.new(0, 72, 0, 80)
creatorLabel.Size = UDim2.new(0, creatorLabel.TextBounds.X, 1, 0)
end
if FFlagFixLoadingScreenJankiness then
coroutine.wrap(function()
RunService.RenderStepped:wait()
RunService.RenderStepped:wait()
layoutIsReady = true
placeLabel.TextTransparency = 0
local uiMessage = uiMessageFrame.UiMessage
if uiMessage.Text ~= "" then
uiMessage.TextTransparency = 0
end
end)()
end
if not FFlagConnectionScriptEnabled then
local errorFrame = create 'Frame' {
Name = 'ErrorFrame',
BackgroundColor3 = COLORS.ERROR,
BorderSizePixel = 0,
Position = UDim2.new(0.25,0,0,0),
Size = UDim2.new(0.5, 0, 0, 80),
ZIndex = 8,
Visible = false,
Parent = screenGui,
create 'TextLabel' {
Name = "ErrorText",
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 1, 0),
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size14,
TextWrapped = true,
TextColor3 = COLORS.TEXT_COLOR,
Text = "",
ZIndex = 8
}
}
end
while not game:GetService("CoreGui") do
if FFlagLoadTheLoadingScreenFaster then
RunService.RenderStepped:wait()
else
wait()
end
end
local CoreGui = game:GetService("CoreGui");
screenGui.Parent = CoreGui
if FFlagLoadingScreenUseLocalizationTable then
infoFrame.RootLocalizationTable = CoreGui:FindFirstChild("CoreScriptLocalization")
end
currScreenGui = screenGui
local function onResized()
local isPortrait = screenGui.AbsoluteSize.X < screenGui.AbsoluteSize.Y
infoFrame.Position = UDim2.new(0.5, 0, 0.5, 0)
infoFrame.AnchorPoint = Vector2.new(0.5, 0.5)
infoFrame.Size = UDim2.new(0.75, 0, 1, 0)
infoFrameAspect.AspectRatio = isPortrait and 2/3 or 3/2
placeLabel.Size = UDim2.new(1, 0, 0, isTenFootInterface and 120 or 80)
if isTenFootInterface then
creatorLabel.Size = UDim2.new(0, creatorLabel.TextBounds.X, 1, 0)
else
creatorLabel.Size = UDim2.new(1, 0, 0, 30)
end
infoFrameList.FillDirection = Enum.FillDirection.Vertical
infoFrameList.HorizontalAlignment = Enum.HorizontalAlignment.Center
infoFrameList.Padding = UDim.new(0, 0)
textContainer.Parent = nil
infoFrameList:ApplyLayout()
placeLabel.TextXAlignment = Enum.TextXAlignment.Center
end
onResized()
screenGui:GetPropertyChangedSignal("AbsoluteSize"):connect(onResized)
end
---------------------------------------------------------
-- Main Script (show something now + setup connections)
-- start loading assets asap
InfoProvider:LoadAssets()
MainGui:GenerateMain()
if isTenFootInterface then
createTenfootCancelGui()
end
local fadeCycleTime = 1.7
local turnCycleTime = 2
local function spinnerEasingFunc(a, b, t)
t = t * 2
if t < 1 then
return b / 2 * t*t*t + a
else
t = t - 2
return b / 2 * (t * t * t + 2) + b
end
end
renderSteppedConnection = RunService.RenderStepped:connect(function(dt)
if not currScreenGui then return end
if not currScreenGui:FindFirstChild("BlackFrame") then return end
local infoFrame = currScreenGui.BlackFrame:FindFirstChild('InfoFrame')
if infoFrame then
-- set place name
if placeLabel and placeLabel.Text == "" then
placeLabel.Text = InfoProvider:GetGameName()
end
-- set creator name
if creatorLabel and creatorLabel.Text == "" then
local creatorName = InfoProvider:GetCreatorName()
if creatorName ~= "" then
if isTenFootInterface then
creatorLabel.Text = creatorName
creatorLabel.Size = UDim2.new(0, creatorLabel.TextBounds.X, 1, 0)
else
creatorLabel.Text = "By ".. creatorName
end
end
end
end
local currentTime = tick()
local fadeAmount = dt * fadeCycleTime
local spinnerImage = currScreenGui.BlackFrame.GraphicsFrame.LoadingImage
local timeInCycle = currentTime % turnCycleTime
local cycleAlpha = spinnerEasingFunc(0, 1, timeInCycle / turnCycleTime)
spinnerImage.Rotation = cycleAlpha * 360
if not isTenFootInterface then
if currentTime - startTime > 5 and currScreenGui.BlackFrame.CloseButton.ImageTransparency > 0 then
currScreenGui.BlackFrame.CloseButton.ImageTransparency = currScreenGui.BlackFrame.CloseButton.ImageTransparency - fadeAmount
if currScreenGui.BlackFrame.CloseButton.ImageTransparency <= 0 then
currScreenGui.BlackFrame.CloseButton.Active = true
end
end
end
end)
if not FFlagConnectionScriptEnabled then
local leaveGameButton, leaveGameTextLabel, errorImage = nil
GuiService.ErrorMessageChanged:connect(function()
if GuiService:GetErrorMessage() ~= '' then
--TODO: Remove this reference to Utility
local utility = require(RobloxGui.Modules.Settings.Utility)
if isTenFootInterface then
currScreenGui.ErrorFrame.Size = UDim2.new(1, 0, 0, 144)
currScreenGui.ErrorFrame.Position = UDim2.new(0, 0, 0, 0)
currScreenGui.ErrorFrame.BackgroundColor3 = COLORS.BACKGROUND_COLOR
currScreenGui.ErrorFrame.BackgroundTransparency = 0.5
currScreenGui.ErrorFrame.ErrorText.FontSize = Enum.FontSize.Size36
currScreenGui.ErrorFrame.ErrorText.Position = UDim2.new(.3, 0, 0, 0)
currScreenGui.ErrorFrame.ErrorText.Size = UDim2.new(.4, 0, 0, 144)
if errorImage == nil then
errorImage = Instance.new("ImageLabel")
errorImage.Image = "rbxasset://textures/ui/ErrorIconSmall.png"
errorImage.Size = UDim2.new(0, 96, 0, 79)
errorImage.Position = UDim2.new(0.228125, 0, 0, 32)
errorImage.ZIndex = 9
errorImage.BackgroundTransparency = 1
errorImage.Parent = currScreenGui.ErrorFrame
end
elseif utility:IsSmallTouchScreen() then
currScreenGui.ErrorFrame.Size = UDim2.new(0.5, 0, 0, 40)
end
local errorCode = GuiService:GetErrorCode()
local errorMessage = GuiService:GetErrorMessage()
if not FFlagShowConnectionErrorCode then
currScreenGui.ErrorFrame.ErrorText.Text = errorMessage
else
if not errorCode then
currScreenGui.ErrorFrame.ErrorText.Text = ("%s (Error Code: -1)"):format(errorMessage)
else
currScreenGui.ErrorFrame.ErrorText.Text = ("%s (Error Code: %d)"):format(errorMessage, errorCode.Value)
end
end
currScreenGui.ErrorFrame.Visible = true
local blackFrame = currScreenGui:FindFirstChild('BlackFrame')
if blackFrame then
blackFrame.CloseButton.ImageTransparency = 0
blackFrame.CloseButton.Active = true
end
else
currScreenGui.ErrorFrame.Visible = false
end
end)
end
GuiService.UiMessageChanged:connect(function(type, newMessage)
if type == Enum.UiMessageType.UiMessageInfo then
local blackFrame = currScreenGui and currScreenGui:FindFirstChild('BlackFrame')
if blackFrame then
local infoFrame = blackFrame:FindFirstChild("InfoFrame")
if FFlagFixLoadingScreenJankiness then
if infoFrame then
local uiMessage = infoFrame.UiMessageFrame.UiMessage
uiMessage.Text = newMessage
if newMessage ~= '' and layoutIsReady then
uiMessage.TextTransparency = 0
else
uiMessage.TextTransparency = 1
end
end
else
if infoFrame then
infoFrame.UiMessageFrame.UiMessage.Text = newMessage
if newMessage ~= '' then
infoFrame.UiMessageFrame.Visible = true
else
infoFrame.UiMessageFrame.Visible = false
end
else
blackFrame.UiMessageFrame.UiMessage.Text = newMessage
if newMessage ~= '' then
blackFrame.UiMessageFrame.Visible = true
else
blackFrame.UiMessageFrame.Visible = false
end
end
end
end
end
end)
if not FFlagConnectionScriptEnabled and GuiService:GetErrorMessage() ~= '' then
currScreenGui.ErrorFrame.ErrorText.Text = GuiService:GetErrorMessage()
currScreenGui.ErrorFrame.Visible = true
end
function stopListeningToRenderingStep()
if renderSteppedConnection then
renderSteppedConnection:disconnect()
renderSteppedConnection = nil
end
end
function disconnectAndCloseHealthStat()
if connectionHealthCon then
connectionHealthCon:disconnect()
connectionHealthCon = nil
GuiService:CloseStatsBasedOnInputString("ConnectionHealth")
end
end
function fadeAndDestroyBlackFrame(blackFrame)
if destroyingBackground then return end
destroyingBackground = true
spawn(function()
local infoFrame = blackFrame:FindFirstChild("InfoFrame")
local graphicsFrame = blackFrame:FindFirstChild("GraphicsFrame")
local function getDescendants(root, children)
children = children or {}
for i, v in pairs(root:GetChildren()) do
children[#children + 1] = v
getDescendants(v, children)
end
return children
end
local infoFrameDescendants = getDescendants(infoFrame)
local transparency = 0
local rateChange = 1.8
local lastUpdateTime = nil
--Notify everything else to stop messing with transparency to avoid ugly fighting effects
backgroundFadeStarted = true
if tweenPlaceIcon then
tweenPlaceIcon:Cancel()
tweenPlaceIcon = nil
end
while transparency < 1 do
RunService.RenderStepped:wait()
if not lastUpdateTime then
lastUpdateTime = tick()
else
local newTime = tick()
transparency = transparency + rateChange * (newTime - lastUpdateTime)
for i = 1, #infoFrameDescendants do
local child = infoFrameDescendants[i]
if child:IsA('TextLabel') then
child.TextTransparency = transparency
elseif child:IsA('ImageLabel') then
child.ImageTransparency = transparency
end
end
graphicsFrame.LoadingImage.ImageTransparency = transparency
blackFrame.BackgroundTransparency = transparency
lastUpdateTime = newTime
end
end
if blackFrame ~= nil then
stopListeningToRenderingStep()
blackFrame:Destroy()
end
loadingImageInputBeganConn:disconnect()
if connectionHealthShown then
if UserInputService.TouchEnabled == true and UserInputService.MouseEnabled == false then
connectionHealthCon = game:GetService("UserInputService").InputBegan:connect(function()
disconnectAndCloseHealthStat()
end)
else
GuiService:CloseStatsBasedOnInputString("ConnectionHealth")
end
else
GuiService:CloseStatsBasedOnInputString("ConnectionHealth")
end
end)
end
function destroyLoadingElements(instant)
if not currScreenGui then return end
if destroyedLoadingGui then return end
destroyedLoadingGui = true
local guiChildren = currScreenGui:GetChildren()
for i=1, #guiChildren do
-- need to keep this around in case we get a connection error later
if guiChildren[i].Name ~= "ErrorFrame" then
if guiChildren[i].Name == "BlackFrame" and not instant then
fadeAndDestroyBlackFrame(guiChildren[i])
else
guiChildren[i]:Destroy()
end
end
end
end
function handleFinishedReplicating()
hasReplicatedFirstElements = (#game:GetService("ReplicatedFirst"):GetChildren() > 0)
if not hasReplicatedFirstElements then
if game:IsLoaded() then
handleRemoveDefaultLoadingGui()
else
local gameLoadedCon = nil
gameLoadedCon = game.Loaded:connect(function()
gameLoadedCon:disconnect()
gameLoadedCon = nil
handleRemoveDefaultLoadingGui()
end)
end
else
wait(5) -- make sure after 5 seconds we remove the default gui, even if the user doesn't
handleRemoveDefaultLoadingGui()
end
end
function handleRemoveDefaultLoadingGui(instant)
if isTenFootInterface then
ContextActionService:UnbindCoreAction('CancelGameLoad')
end
destroyLoadingElements(instant)
game:GetService("ReplicatedFirst"):SetDefaultLoadingGuiRemoved()
end
if debugMode then
warn("Not destroying loading screen because debugMode is true")
return
end
game:GetService("ReplicatedFirst").FinishedReplicating:connect(handleFinishedReplicating)
if game:GetService("ReplicatedFirst"):IsFinishedReplicating() then
handleFinishedReplicating()
end
game:GetService("ReplicatedFirst").RemoveDefaultLoadingGuiSignal:connect(handleRemoveDefaultLoadingGui)
if game:GetService("ReplicatedFirst"):IsDefaultLoadingGuiRemoved() then
handleRemoveDefaultLoadingGui()
end
local VREnabledConn
local function onVREnabled()
if VRService.VREnabled then
handleRemoveDefaultLoadingGui(true)
require(RobloxGui.Modules.LoadingScreen3D)
end
end
VREnabledConn = VRService:GetPropertyChangedSignal("VREnabled"):connect(onVREnabled)
onVREnabled()
@@ -0,0 +1,33 @@
-- >= left < mid, >= mid <= right
function bottomupmerge(comp, a, b, left, mid, right)
local i, j = left, mid
for k = left, right do
if i < mid and (j > right or not comp(a[j], a[i])) then
b[k] = a[i]
i = i + 1
else
b[k] = a[j]
j = j + 1
end
end
end
function mergesort(arr, comp)
local work = {}
for i = 1, #arr do
work[i] = arr[i]
end
local width = 1
while width < #arr do
for i = 1, #arr, 2*width do
bottomupmerge(comp, arr, work, i, math.min(i+width, #arr), math.min(i+2*width-1, #arr))
end
local temp = work
work = arr
arr = temp
width = width * 2
end
return arr
end
return mergesort(...)
@@ -0,0 +1,460 @@
local CoreGui = game:GetService("CoreGui")
local ContextActionService = game:GetService("ContextActionService")
local TweenService = game:GetService("TweenService")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Utility = require(RobloxGui.Modules.Settings.Utility)
local INPUT_TYPE_ROW_HEIGHT = 30
local ACTION_ROW_HEIGHT = 20
local ROW_PADDING = 5
local COLUMN_PADDING = 5
local CORE_SECURITY_COLUMN_COLOR = Color3.new(0.1, 0, 0)
local DEV_SECURITY_COLUMN_COLOR = Color3.new(0, 0, 0)
local EXPAND_ROTATE_IMAGE_TWEEN_OUT = TweenInfo.new(0.150, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local EXPAND_ROTATE_IMAGE_TWEEN_IN = TweenInfo.new(0.150, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local ROW_PULSE = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, 0, true, 0)
local CONTAINER_SCROLL = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
local container = nil
local boundInputTypeRows = {}
local boundInputTypesByRows = {}
local boundInputTypeActionRows = {}
local boundActionInfoByRows = {}
local inputTypesByActionRows = {}
local inputTypesByHeaders = {}
local headersByInputTypes = {}
local inputTypesExpanded = {}
local layoutOrderDirty = true
local rowTypePrecedence = {
BoundInputType = 3,
TableHeader = 2,
BoundAction = 1
}
local function sortInputTypeRows(a, b)
local inputTypeA = boundInputTypesByRows[a]
local inputTypeB = boundInputTypesByRows[b]
return tostring(inputTypeA) < tostring(inputTypeB)
end
local function sortActionRows(a, b)
local actionA = boundActionInfoByRows[a]
local actionB = boundActionInfoByRows[b]
if actionA and actionB then
local rowInputTypeA = inputTypesByActionRows[a]
local rowInputTypeB = inputTypesByActionRows[b]
if rowInputTypeA ~= rowInputTypeB then
return tostring(rowInputTypeA) < tostring(rowInputTypeB)
end
if actionA.isCore and not actionB.isCore then
return true
elseif not actionA.isCore and actionB.isCore then
return false
end
local stackOrderA = actionA.stackOrder
local stackOrderB = actionB.stackOrder
if stackOrderA and stackOrderB then
return stackOrderA > stackOrderB --descending sort
else
return true
end
else
return true
end
return true
end
local function createEmptyRow(name, height)
local row = Utility:Create("Frame") {
Name = name,
BackgroundTransparency = 1,
ZIndex = 6,
Size = UDim2.new(1, 0, 0, height or 0)
}
local columnList = Utility:Create("UIListLayout") {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, COLUMN_PADDING),
Parent = row
}
return row
end
local function createButtonRow(name, height)
local row = Utility:Create("TextButton") {
Name = name,
BackgroundTransparency = 1,
ZIndex = 6,
Text = "",
Size = UDim2.new(1, 0, 0, height or 0),
}
local columnList = Utility:Create("UIListLayout") {
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, COLUMN_PADDING),
Parent = row
}
return row
end
local function createEmptyColumn(row, columnName)
local column = Utility:Create("Frame") {
Name = columnName,
BackgroundColor3 = Color3.new(0, 0, 0),
BackgroundTransparency = 0.75,
BorderSizePixel = 0,
Size = UDim2.new(1, 0, 1, 0),
ZIndex = 6,
ClipsDescendants = true,
Parent = row
}
return column
end
local function createImageColumn(row, columnName, image, aspectRatio, imageSize)
local column = createEmptyColumn(row, columnName)
local aspectRatioConstraint = Utility:Create("UIAspectRatioConstraint") {
AspectRatio = aspectRatio or 1,
Parent = column
}
local imageLabel = Utility:Create("ImageLabel") {
Name = "ColumnImage",
BackgroundTransparency = 1,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(imageSize or 1, 0, imageSize or 1, 0),
AnchorPoint = Vector2.new(0.5, 0.5),
ZIndex = 6,
Image = image,
Parent = column
}
return column
end
local function createTextColumn(row, columnName, text)
local column = createEmptyColumn(row, columnName)
local textLabel = Utility:Create("TextLabel") {
Name = "ColumnText",
BackgroundTransparency = 1,
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(1, -10, 1, -10),
AnchorPoint = Vector2.new(0.5, 0.5),
ZIndex = 6,
Text = text,
TextSize = 18,
TextColor3 = Color3.new(1, 1, 1),
TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.SourceSans,
Parent = column
}
return column
end
local function createActionColumns(row, backgroundColor)
local x = 0
local insetWidth = ACTION_ROW_HEIGHT + (COLUMN_PADDING * 2)
local insetCol = createEmptyColumn(row, "Inset")
insetCol.LayoutOrder = 0
insetCol.BackgroundTransparency = 1
insetCol.Size = UDim2.new(0, insetWidth, 1, 0)
x = x + insetWidth + COLUMN_PADDING
local priorityWidth = 80
local priorityCol = createTextColumn(row, "Priority", "Priority")
priorityCol.LayoutOrder = 1
priorityCol.BackgroundColor3 = backgroundColor
priorityCol.Size = UDim2.new(0, priorityWidth, 1, 0)
x = x + priorityWidth + COLUMN_PADDING
local securityWidth = 80
local securityCol = createTextColumn(row, "Security", "Security")
securityCol.LayoutOrder = 2
securityCol.BackgroundColor3 = backgroundColor
securityCol.Size = UDim2.new(0, securityWidth, 1, 0)
x = x + securityWidth + COLUMN_PADDING
local nameCol = createTextColumn(row, "ActionName", "Action Name")
nameCol.LayoutOrder = 3
nameCol.BackgroundColor3 = backgroundColor
nameCol.Size = UDim2.new(1/4, 0, 1, 0)
local inputTypesCol = createTextColumn(row, "InputTypes", "Input Types")
inputTypesCol.LayoutOrder = 4
inputTypesCol.BackgroundColor3 = backgroundColor
inputTypesCol.Size = UDim2.new(3/4, -x - COLUMN_PADDING, 1, 0)
return insetCol, priorityCol, securityCol, nameCol, inputTypesCol
end
local function updateContainerCanvas()
debug.profilebegin("updateContainerCanvas")
if layoutOrderDirty then
layoutOrderDirty = false
local idx = 1
local inputTypeRowList = {}
for inputType, inputTypeRow in pairs(boundInputTypeRows) do
table.insert(inputTypeRowList, inputTypeRow)
end
table.sort(inputTypeRowList, sortInputTypeRows)
for i, inputTypeRow in pairs(inputTypeRowList) do
local inputType = boundInputTypesByRows[inputTypeRow]
inputTypeRow.LayoutOrder = idx; idx = idx + 1
local headerRow = headersByInputTypes[inputType]
if headerRow then
headerRow.LayoutOrder = idx; idx = idx + 1
end
local actionRows = boundInputTypeActionRows[inputType]
if actionRows then
local actionRowList = {}
for actionName, actionRow in pairs(actionRows) do
table.insert(actionRowList, actionRow)
end
table.sort(actionRowList, sortActionRows)
for i, actionRow in pairs(actionRowList) do
actionRow.LayoutOrder = idx; idx = idx + 1
end
end
end
end
debug.profileend()
end
local function scrollContainerToRow(row)
local scrollOffset = row.AbsolutePosition.Y - container.AbsolutePosition.Y
local newCanvasPosition = container.CanvasPosition + Vector2.new(0, scrollOffset)
TweenService:Create(container, CONTAINER_SCROLL, { CanvasPosition = newCanvasPosition }):Play()
end
local ActionBindingsTab = {}
function ActionBindingsTab.initializeGui(tabFrame)
local scrollingFrame = Utility:Create("ScrollingFrame") {
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(1, -10, 1, -10),
AnchorPoint = Vector2.new(0.5, 0.5),
BorderSizePixel = 0,
ScrollBarThickness = 4,
BackgroundTransparency = 1,
ZIndex = 6,
Parent = tabFrame
}
container = scrollingFrame
local listLayout = Utility:Create("UIListLayout") {
Padding = UDim.new(0, ROW_PADDING),
Parent = scrollingFrame
}
listLayout.SortOrder = Enum.SortOrder.LayoutOrder
listLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() container.CanvasSize = UDim2.new(0, 0, 0, listLayout.AbsoluteContentSize.Y) end)
ActionBindingsTab.updateGuis()
ContextActionService.BoundActionAdded:connect(function(actionName, createTouchButton, actionInfo, isCore)
actionInfo.isCore = isCore
ActionBindingsTab.updateActionRows(actionName, actionInfo)
layoutOrderDirty = true
updateContainerCanvas()
end)
ContextActionService.BoundActionRemoved:connect(function(actionName, actionInfo, isCore)
actionInfo.isCore = isCore
ActionBindingsTab.removeActionRows(actionName, actionInfo)
layoutOrderDirty = true
updateContainerCanvas()
end)
end
function ActionBindingsTab.updateBoundInputTypeRow(inputType)
local existingRow = boundInputTypeRows[inputType]
if not existingRow then
local row = createButtonRow("BoundInputType", INPUT_TYPE_ROW_HEIGHT)
local expandImageCol = createImageColumn(row, "ExpandImage", "rbxasset://textures/ui/ExpandArrowSheet.png", 1, 0.35)
expandImageCol.ColumnImage.ImageRectSize = Vector2.new(21, 21)
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(0, 0)
local inputTypeCol = createTextColumn(row, "InputType", tostring(inputType))
inputTypeCol.Size = UDim2.new(1, -INPUT_TYPE_ROW_HEIGHT - COLUMN_PADDING, 1, 0)
inputTypeCol.ColumnText.Font = Enum.Font.SourceSansBold
local tableHeaderRow = createEmptyRow("TableHeader", ACTION_ROW_HEIGHT)
tableHeaderRow.Visible = false
local _, priorityCol, securityCol, nameCol, inputTypesCol = createActionColumns(tableHeaderRow, DEV_SECURITY_COLUMN_COLOR)
priorityCol.ColumnText.Font = Enum.Font.SourceSansBold
securityCol.ColumnText.Font = Enum.Font.SourceSansBold
nameCol.ColumnText.Font = Enum.Font.SourceSansBold
inputTypesCol.ColumnText.Font = Enum.Font.SourceSansBold
boundInputTypeRows[inputType] = row
boundInputTypesByRows[row] = inputType
inputTypesByHeaders[tableHeaderRow] = inputType
headersByInputTypes[inputType] = tableHeaderRow
tableHeaderRow.Parent = container
row.Parent = container
TweenService:Create(inputTypeCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
inputTypesExpanded[inputType] = false
row.MouseButton1Click:connect(function()
inputTypesExpanded[inputType] = not inputTypesExpanded[inputType]
local inputTypeActionRows = boundInputTypeActionRows[inputType]
if not inputTypesExpanded[inputType] then
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(0, 0)
tableHeaderRow.Visible = false
if inputTypeActionRows then
for _, actionRow in pairs(inputTypeActionRows) do
actionRow.Visible = false
end
end
else
expandImageCol.ColumnImage.ImageRectOffset = Vector2.new(21, 0)
tableHeaderRow.Visible = true
if inputTypeActionRows then
for _, actionRow in pairs(inputTypeActionRows) do
actionRow.Visible = true
end
end
end
updateContainerCanvas()
if inputTypesExpanded[inputType] then
TweenService:Create(inputTypeCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
scrollContainerToRow(row)
end
end)
end
end
function ActionBindingsTab.updateActionRowForInputType(actionName, actionInfo, inputType)
local inputTypeActionRows = boundInputTypeActionRows[inputType]
if not inputTypeActionRows then
inputTypeActionRows = {}
boundInputTypeActionRows[inputType] = inputTypeActionRows
end
local existingRow = inputTypeActionRows[actionName]
if not existingRow then
local row = createEmptyRow("BoundAction", ACTION_ROW_HEIGHT)
row.Visible = inputTypesExpanded[inputType]
local inputTypeNames = {}
for i, inputType in pairs(actionInfo.inputTypes) do
inputTypeNames[i] = tostring(inputType)
end
local insetCol, priorityCol, securityCol, nameCol, inputTypesCol = createActionColumns(row, actionInfo.isCore and CORE_SECURITY_COLUMN_COLOR or DEV_SECURITY_COLUMN_COLOR)
priorityCol.ColumnText.Text = actionInfo.priorityLevel or "Default"
securityCol.ColumnText.Text = actionInfo.isCore and "Core" or "Developer"
nameCol.ColumnText.Text = actionName
inputTypesCol.ColumnText.Text = table.concat(inputTypeNames, ", ")
if actionInfo.isCore then
priorityCol.ColumnText.Font = Enum.Font.SourceSansItalic
securityCol.ColumnText.Font = Enum.Font.SourceSansItalic
nameCol.ColumnText.Font = Enum.Font.SourceSansItalic
inputTypesCol.ColumnText.Font = Enum.Font.SourceSansItalic
end
inputTypeActionRows[actionName] = row
inputTypesByActionRows[row] = inputType
boundActionInfoByRows[row] = actionInfo
row.Parent = container
if row.Visible then
TweenService:Create(nameCol, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
else
local inputTypeRow = boundInputTypeRows[inputType]
if inputTypeRow then
TweenService:Create(inputTypeRow.InputType, ROW_PULSE, { BackgroundColor3 = Color3.new(0.5, 0.5, 0.5) }):Play()
end
end
existingRow = row
end
end
function ActionBindingsTab.updateActionRows(actionName, actionInfo)
for _, inputType in pairs(actionInfo.inputTypes) do
ActionBindingsTab.updateBoundInputTypeRow(inputType)
ActionBindingsTab.updateActionRowForInputType(actionName, actionInfo, inputType)
end
end
function ActionBindingsTab.removeActionRows(actionName, actionInfo)
for _, inputType in pairs(actionInfo.inputTypes) do
local inputTypeActionRows = boundInputTypeActionRows[inputType]
if inputTypeActionRows then
local row = inputTypeActionRows[actionName]
row:Destroy()
inputTypeActionRows[actionName] = nil
--The following code looks weird. It's because Lua has no way to determine
--if a table is explicitly empty in both the array and dictionary parts.
--This does it though.
local isEmpty = true
for _, __ in pairs(inputTypeActionRows) do
isEmpty = false
break
end
if isEmpty then
local inputTypeRow = boundInputTypeRows[inputType]
if inputTypeRow then
inputTypeRow:Destroy()
boundInputTypeRows[inputType] = nil
end
local tableHeaderRow = headersByInputTypes[inputType]
if tableHeaderRow then
headersByInputTypes[tableHeaderRow] = nil
tableHeaderRow:Destroy()
headersByInputTypes[inputType] = nil
end
end
end
end
updateContainerCanvas()
container.UIListLayout:ApplyLayout()
end
function ActionBindingsTab.updateGuis()
local boundCoreActions = ContextActionService:GetAllBoundCoreActionInfo()
for actionName, actionInfo in pairs(boundCoreActions) do
actionInfo.isCore = true
ActionBindingsTab.updateActionRows(actionName, actionInfo)
end
local boundActions = ContextActionService:GetAllBoundActionInfo()
for actionName, actionInfo in pairs(boundActions) do
actionInfo.isCore = false
ActionBindingsTab.updateActionRows(actionName, actionInfo)
end
layoutOrderDirty = true
updateContainerCanvas()
end
return ActionBindingsTab
@@ -0,0 +1,269 @@
--[[
// FileName: ContextMenuGui.lua
// Written by: TheGamer101
// Description: Module for creating the context GUI.
]]
-- CONSTANTS
local BG_TRANSPARENCY = 1
local BG_COLOR = Color3.fromRGB(31, 31, 31)
local BOTTOM_SCREEN_PADDING_PERCENT = 0.02
local MAX_WIDTH = 250
local MAX_HEIGHT = 300
local MAX_WIDTH_PERCENT = 0.7
local MAX_HEIGHT_PERCENT = 0.6
local PLAYER_ICON_SIZE_Y = 0.3
-- SERVICES
local CoreGuiService = game:GetService("CoreGui")
local GuiService = game:GetService("GuiService")
--- VARIABLES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local SettingsModules = CoreGuiModules:WaitForChild("Settings")
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
local PlayerCarousel = nil
local PlayerChangedEvent = Instance.new("BindableEvent")
--- Modules
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
local Utility = require(SettingsModules:WaitForChild("Utility"))
local ContextMenuGui = {}
ContextMenuGui.__index = ContextMenuGui
-- PRIVATE METHODS
function ContextMenuGui:CreateContextMenuHolder(player)
local contextMenuHolder = Instance.new("Frame")
contextMenuHolder.Name = "AvatarContextMenu"
contextMenuHolder.Position = UDim2.new(0, 0, 0, 0)
contextMenuHolder.Size = UDim2.new(1, 0, 1, 0)
contextMenuHolder.BackgroundTransparency = 1
contextMenuHolder.Parent = RobloxGui
return contextMenuHolder
end
function ContextMenuGui:CreateLeaveMenuButton(frame)
local function closeMenu()
self.CloseMenuFunc()
end
local closeMenuButton = Instance.new("ImageButton")
closeMenuButton.Name = "CloseMenuButton"
closeMenuButton.BackgroundTransparency = 1
closeMenuButton.AnchorPoint = Vector2.new(1, 0)
closeMenuButton.Position = UDim2.new(1, -10, 0, 10)
closeMenuButton.Size = UDim2.new(0.05, 0, 0.1, 0)
closeMenuButton.Image = "rbxasset://textures/loading/cancelButton.png"
closeMenuButton.Selectable = false
closeMenuButton.Activated:Connect(closeMenu)
local aspectConstraint = Instance.new("UIAspectRatioConstraint")
aspectConstraint.AspectType = Enum.AspectType.FitWithinMaxSize
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
aspectConstraint.AspectRatio = 1
aspectConstraint.Parent = closeMenuButton
closeMenuButton.Parent = frame
return closeMenuButton
end
-- PUBLIC METHODS
local function listenToViewportChange(functionToFire)
if functionToFire == nil then return end
local viewportChangedConnection = nil
local function updateCamera()
local newCamera = workspace.CurrentCamera
if viewportChangedConnection then
viewportChangedConnection:Disconnect()
end
viewportChangedConnection = newCamera:GetPropertyChangedSignal('ViewportSize'):Connect(functionToFire)
functionToFire()
end
workspace:GetPropertyChangedSignal('CurrentCamera'):Connect(updateCamera)
updateCamera()
end
function ContextMenuGui:CreateMenuFrame()
local contextMenuHolder = self:CreateContextMenuHolder()
local menu = Instance.new("ImageButton")
menu.Name = "Menu"
menu.Size = UDim2.new(0.95, 0, 0.9, 0)
menu.Position = UDim2.new(0.5, 0, 1 - BOTTOM_SCREEN_PADDING_PERCENT, 0)
menu.AnchorPoint = Vector2.new(0.5, 1)
menu.BackgroundTransparency = 1
menu.Selectable = false
menu.Image = "rbxasset://textures/blackBkg_round.png"
menu.ScaleType = Enum.ScaleType.Slice
menu.SliceCenter = Rect.new(12,12,12,12)
menu.Visible = false
menu.Active = true
menu.ClipsDescendants = true
GuiService:AddSelectionParent("AvatarContextMenuGroup", menu)
local aspectConstraint = Instance.new("UIAspectRatioConstraint")
aspectConstraint.AspectType = Enum.AspectType.ScaleWithParentSize
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
aspectConstraint.AspectRatio = 1.15
aspectConstraint.Parent = menu
local function updateAspectRatioForViewport()
local viewportSize = workspace.CurrentCamera.ViewportSize
if viewportSize.x < viewportSize.y then
aspectConstraint.DominantAxis = Enum.DominantAxis.Width
else
aspectConstraint.DominantAxis = Enum.DominantAxis.Height
end
end
listenToViewportChange(updateAspectRatioForViewport)
local sizeConstraint = Instance.new("UISizeConstraint")
sizeConstraint.MaxSize = Vector2.new(300,300)
sizeConstraint.MinSize = Vector2.new(200,200)
sizeConstraint.Parent = menu
local contentFrame = Instance.new("Frame")
contentFrame.Name = "Content"
contentFrame.Size = UDim2.new(1,0,1,0)
contentFrame.BackgroundTransparency = 1
contentFrame.Parent = menu
local contentListLayout = Instance.new("UIListLayout")
contentListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
contentListLayout.VerticalAlignment = Enum.VerticalAlignment.Top
contentListLayout.SortOrder = Enum.SortOrder.LayoutOrder
contentListLayout.Parent = contentFrame
local contextActionList = Instance.new("ScrollingFrame")
contextActionList.Name = "ContextActionList"
contextActionList.AnchorPoint = Vector2.new(0.5,1)
contextActionList.BackgroundColor3 = Color3.fromRGB(79,79,79)
contextActionList.BorderSizePixel = 0
contextActionList.LayoutOrder = 2
contextActionList.Size = UDim2.new(1,-12,0.54,0)
contextActionList.CanvasSize = UDim2.new(0,0,0,208)
contextActionList.ScrollBarThickness = 4
contextActionList.Selectable = false
contextActionList.Parent = contentFrame
local contextActionListUIListLayout = Instance.new("UIListLayout")
contextActionListUIListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
contextActionListUIListLayout.SortOrder = Enum.SortOrder.LayoutOrder
contextActionListUIListLayout.VerticalAlignment = Enum.VerticalAlignment.Top
contextActionListUIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
contextActionList.CanvasSize = UDim2.new(0,0,0,contextActionListUIListLayout.AbsoluteContentSize.Y)
end)
contextActionListUIListLayout.Parent = contextActionList
local nameTag = Instance.new("TextButton")
nameTag.Name = "NameTag"
nameTag.AnchorPoint = Vector2.new(0.5,1)
nameTag.BackgroundColor3 = Color3.fromRGB(79,79,79)
nameTag.AutoButtonColor = false
nameTag.BorderSizePixel = 0
nameTag.LayoutOrder = 1
nameTag.Size = UDim2.new(1,-12,0.16,0)
nameTag.Font = Enum.Font.SourceSansBold
nameTag.Text = ""
nameTag.TextColor3 = Color3.fromRGB(255,255,255)
nameTag.TextSize = 24
nameTag.TextXAlignment = Enum.TextXAlignment.Center
nameTag.TextYAlignment = Enum.TextYAlignment.Center
nameTag.Selectable = false
nameTag.Parent = contentFrame
local underline = Instance.new("Frame")
underline.Name = "Underline"
underline.BackgroundColor3 = Color3.fromRGB(255,255,255)
underline.AnchorPoint = Vector2.new(0,1)
underline.BorderSizePixel = 0
underline.Position = UDim2.new(0,0,1,0)
underline.Size = UDim2.new(1,0,0,2)
underline.Parent = nameTag
self:CreateLeaveMenuButton(menu)
menu.Parent = contextMenuHolder
self.ContextMenuFrame = menu
return menu
end
function ContextMenuGui:BuildPlayerCarousel(playersByProximity)
if not PlayerCarousel then
PlayerCarousel = require(AvatarMenuModules:WaitForChild("PlayerCarousel"))
PlayerCarousel.rbxGui.Parent = self.ContextMenuFrame.Content
end
PlayerCarousel:ClearPlayerEntries()
for i = 1, #playersByProximity do
PlayerCarousel:CreatePlayerEntry(playersByProximity[i][1], playersByProximity[i][2])
end
if #playersByProximity > 0 then
self.ContextMenuFrame.Content.NameTag.Text = playersByProximity[1][1].Name
end
PlayerCarousel.PlayerChanged:Connect(function(player)
if player then
self.ContextMenuFrame.Content.NameTag.Text = player.Name
else
self.ContextMenuFrame.Content.NameTag.Text = ""
end
PlayerChangedEvent:Fire(player)
end)
end
function ContextMenuGui:GetBottomScreenPaddingConstant()
return BOTTOM_SCREEN_PADDING_PERCENT
end
function ContextMenuGui:SetCloseMenuFunc(closeMenuFunc)
self.CloseMenuFunc = closeMenuFunc
end
function ContextMenuGui:SwitchToPlayerEntry(player, dontTween)
if not PlayerCarousel then return end
PlayerCarousel:SwitchToPlayerEntry(player, dontTween)
end
function ContextMenuGui:OffsetPlayerEntry(offset)
if not PlayerCarousel then return end
PlayerCarousel:OffsetPlayerEntry(offset)
end
function ContextMenuGui:GetSelectedPlayer()
if not PlayerCarousel then return nil end
return PlayerCarousel:GetSelectedPlayer()
end
function ContextMenuGui.new()
local obj = setmetatable({}, ContextMenuGui)
obj.CloseMenuFunc = nil
obj.ContextMenuFrame = nil
obj.LastSetPlayerIcon = nil
obj.SelectedPlayerChanged = PlayerChangedEvent.Event
return obj
end
return ContextMenuGui.new()
@@ -0,0 +1,314 @@
--[[
// FileName: ContextMenuItems.lua
// Written by: TheGamer101
// Description: Module for creating the context menu items for the menu and doing the actions when they are clicked.
]]
-- CONSTANTS
local FRIEND_LAYOUT_ORDER = 1
local CHAT_LAYOUT_ORDER = 3
local WAVE_LAYOUT_ORDER = 4
local CUSTOM_LAYOUT_ORDER = 20
local MENU_ITEM_SIZE_X = 0.96
local MENU_ITEM_SIZE_Y = 0
local MENU_ITEM_SIZE_Y_OFFSET = 52
local THUMBNAIL_URL = "https://www.roblox.com/Thumbs/Avatar.ashx?x=200&y=200&format=png&userId="
local BUST_THUMBNAIL_URL = "https://www.roblox.com/bust-thumbnail/image?width=420&height=420&format=png&userId="
--- SERVICES
local PlayersService = game:GetService("Players")
local CoreGuiService = game:GetService("CoreGui")
local StarterGui = game:GetService("StarterGui")
local Chat = game:GetService("Chat")
local RunService = game:GetService("RunService")
local AnalyticsService = game:GetService("AnalyticsService")
-- MODULES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local SettingsModules = CoreGuiModules:WaitForChild("Settings")
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
local SettingsPages = SettingsModules:WaitForChild("Pages")
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
local PromptCreator = require(CoreGuiModules:WaitForChild("PromptCreator"))
local PlayerDropDownModule = require(CoreGuiModules:WaitForChild("PlayerDropDown"))
local ReportAbuseMenu = require(SettingsPages:WaitForChild("ReportAbuseMenu"))
-- VARIABLES
local LocalPlayer = PlayersService.LocalPlayer
while not LocalPlayer do
PlayersService.PlayerAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local EnabledContextMenuItems = {
[Enum.AvatarContextMenuOption.Chat] = true,
[Enum.AvatarContextMenuOption.Friend] = true,
[Enum.AvatarContextMenuOption.Emote] = true
}
local CustomContextMenuItems = {}
local BlockingUtility = PlayerDropDownModule:CreateBlockingUtility()
local ContextMenuItems = {}
ContextMenuItems.__index = ContextMenuItems
-- PRIVATE METHODS
function ContextMenuItems:ClearMenuItems()
local children = self.MenuItemFrame:GetChildren()
for i = 1, #children do
if children[i]:IsA("GuiObject") then
children[i]:Destroy()
end
end
end
function ContextMenuItems:AddCustomAvatarMenuItem(menuOption, bindableEvent)
CustomContextMenuItems[menuOption] = bindableEvent
end
function ContextMenuItems:RemoveCustomAvatarMenuItem(menuOption)
CustomContextMenuItems[menuOption] = nil
end
function ContextMenuItems:IsContextAvatarEnumItem(enumItem)
local enumItems = Enum.AvatarContextMenuOption:GetEnumItems()
for i = 1, #enumItems do
if enumItem == enumItems[i] then
return true
end
end
return false
end
function ContextMenuItems:EnableDefaultMenuItem(menuOption)
EnabledContextMenuItems[menuOption] = true
end
function ContextMenuItems:RemoveDefaultMenuItem(menuOption)
EnabledContextMenuItems[menuOption] = false
end
function ContextMenuItems:RegisterCoreMethods()
local function addMenuItemFunc(args) --[[ menuOption, bindableEvent]]
if type(args) == "table" then
local name = ""
if args[1] and type(args[1]) == "string" then
name = args[1]
else
error("AddAvatarContextMenuOption first argument must be a table or Enum.AvatarContextMenuOption")
end
if args[2] and typeof(args[2]) == "Instance" and args[2].ClassName == "BindableEvent" then
self:AddCustomAvatarMenuItem(name, args[2])
else
error("AddAvatarContextMenuOption second table entry must be a BindableEvent")
end
elseif typeof(args) == "EnumItem" then
if self:IsContextAvatarEnumItem(args) then
self:EnableDefaultMenuItem(args)
else
error("AddAvatarContextMenuOption given EnumItem is not valid")
end
else
error("AddAvatarContextMenuOption first argument must be a table or Enum.AvatarContextMenuOption")
end
end
StarterGui:RegisterSetCore("AddAvatarContextMenuOption", addMenuItemFunc)
local function removeMenuItemFunc(menuOption)
if type(menuOption) == "string" then
self:RemoveCustomAvatarMenuItem(menuOption)
elseif typeof(menuOption) == "EnumItem" then
if self:IsContextAvatarEnumItem(menuOption) then
self:RemoveDefaultMenuItem(menuOption)
else
error("RemoveAvatarContextMenuOption given EnumItem is not valid")
end
else
error("RemoveAvatarContextMenuOption first argument must be a string or Enum.AvatarContextMenuOption")
end
end
StarterGui:RegisterSetCore("RemoveAvatarContextMenuOption", removeMenuItemFunc)
end
function ContextMenuItems:CreateCustomMenuItems()
for buttonText, bindableEvent in pairs(CustomContextMenuItems) do
AnalyticsService:TrackEvent("Game", "AvatarContextMenuCustomButton", "name: " .. tostring(buttonText))
local function customButtonFunc()
bindableEvent:Fire(self.SelectedPlayer)
end
local customButton = ContextMenuUtil:MakeStyledButton("CustomButton", buttonText, UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET), customButtonFunc)
customButton.Name = "CustomButton"
customButton.LayoutOrder = CUSTOM_LAYOUT_ORDER
customButton.Parent = self.MenuItemFrame
end
end
-- PUBLIC METHODS
local addFriendString = "Add Friend"
local friendsString = "Friends"
local friendRequestPendingString = "Friend Request Pending"
local acceptFriendRequestString = "Accept Friend Request"
local addFriendDisabledTransparency = 0.75
local friendStatusChangedConn = nil
function ContextMenuItems:CreateFriendButton(status)
local friendLabel = self.MenuItemFrame:FindFirstChild("FriendStatus")
if friendLabel then
friendLabel:Destroy()
friendLabel = nil
end
if friendStatusChangedConn then
friendStatusChangedConn:disconnect()
end
local friendLabelText = nil
local addFriendFunc = function()
if friendLabelText and friendLabel.Selectable then
friendLabel.Selectable = false
friendLabelText.TextTransparency = addFriendDisabledTransparency
friendLabelText.Text = friendRequestPendingString
AnalyticsService:ReportCounter("AvatarContextMenu-RequestFriendship")
AnalyticsService:TrackEvent("Game", "RequestFriendship", "AvatarContextMenu")
LocalPlayer:RequestFriendship(self.SelectedPlayer)
end
end
friendLabel, friendLabelText = ContextMenuUtil:MakeStyledButton("FriendStatus", addFriendString, UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET), addFriendFunc)
if status ~= Enum.FriendStatus.Friend then
friendLabel.Selectable = true
friendLabelText.TextTransparency = 0
else
friendLabel.Selectable = false
friendLabelText.TextTransparency = addFriendDisabledTransparency
friendLabelText.Text = friendsString
end
friendStatusChangedConn = LocalPlayer.FriendStatusChanged:connect(function(player, friendStatus)
if player == self.SelectedPlayer and friendLabelText then
if not friendLabel.Selectable then
if friendStatus == Enum.FriendStatus.Friend then
friendLabelText.Text = friendsString
end
else
if friendStatus == Enum.FriendStatus.FriendRequestReceived then
friendLabelText.Text = acceptFriendRequestString
end
end
end
end)
friendLabel.LayoutOrder = FRIEND_LAYOUT_ORDER
friendLabel.Parent = self.MenuItemFrame
end
function ContextMenuItems:UpdateFriendButton(status)
local friendLabel = self.MenuItemFrame:FindFirstChild("FriendStatus")
if friendLabel then
self:CreateFriendButton(status)
end
end
function ContextMenuItems:CreateEmoteButton()
local function wave()
if self.CloseMenuFunc then self:CloseMenuFunc() end
AnalyticsService:ReportCounter("AvatarContextMenu-Wave")
AnalyticsService:TrackEvent("Game", "AvatarContextMenuWave", "placeId: " .. tostring(game.PlaceId))
PlayersService:Chat("/e wave")
end
local waveButton = self.MenuItemFrame:FindFirstChild("Wave")
if not waveButton then
waveButton = ContextMenuUtil:MakeStyledButton("Wave", "Wave", UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET), wave)
waveButton.LayoutOrder = WAVE_LAYOUT_ORDER
waveButton.Parent = self.MenuItemFrame
end
end
function ContextMenuItems:CreateChatButton()
local function chatFunc()
if self.CloseMenuFunc then self:CloseMenuFunc() end
AnalyticsService:ReportCounter("AvatarContextMenu-Chat")
AnalyticsService:TrackEvent("Game", "AvatarContextMenuChat", "placeId: " .. tostring(game.PlaceId))
-- todo: need a proper api to set up text in the chat bar
local ChatBar = nil
pcall(function() ChatBar = LocalPlayer.PlayerGui.Chat.Frame.ChatBarParentFrame.Frame.BoxFrame.Frame.ChatBar end)
if ChatBar then
ChatBar.Text = "/w " .. self.SelectedPlayer.Name
end
local ChatModule = require(RobloxGui.Modules.ChatSelector)
ChatModule:SetVisible(true)
ChatModule:FocusChatBar()
end
local chatButton = self.MenuItemFrame:FindFirstChild("ChatStatus")
if not chatButton then
chatButton = ContextMenuUtil:MakeStyledButton("ChatStatus", "Chat", UDim2.new(MENU_ITEM_SIZE_X, 0, MENU_ITEM_SIZE_Y, MENU_ITEM_SIZE_Y_OFFSET), chatFunc)
chatButton.LayoutOrder = CHAT_LAYOUT_ORDER
end
local success, canLocalUserChat = pcall(function() return Chat:CanUserChatAsync(LocalPlayer.UserId) end)
local canChat = success and (RunService:IsStudio() or canLocalUserChat)
if canChat then
chatButton.Parent = self.MenuItemFrame
else
chatButton.Parent = nil
end
end
function ContextMenuItems:BuildContextMenuItems(player)
if not player then return end
local friendStatus = ContextMenuUtil:GetFriendStatus(player)
local isBlocked = BlockingUtility:IsPlayerBlockedByUserId(player.UserId)
local isMuted = BlockingUtility:IsPlayerMutedByUserId(player.UserId)
self:ClearMenuItems()
self:SetSelectedPlayer(player)
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Friend] then
self:CreateFriendButton(friendStatus)
end
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Chat] then
self:CreateChatButton()
end
if EnabledContextMenuItems[Enum.AvatarContextMenuOption.Emote] then
self:CreateEmoteButton()
end
self:CreateCustomMenuItems()
end
function ContextMenuItems:SetSelectedPlayer(selectedPlayer)
self.SelectedPlayer = selectedPlayer
end
function ContextMenuItems:SetCloseMenuFunc(closeMenuFunc)
self.CloseMenuFunc = closeMenuFunc
end
function ContextMenuItems.new(menuItemFrame)
local obj = setmetatable({}, ContextMenuItems)
obj.MenuItemFrame = menuItemFrame
obj.SelectedPlayer = nil
obj:RegisterCoreMethods()
return obj
end
return ContextMenuItems
@@ -0,0 +1,278 @@
--[[
// FileName: ContextMenuUtil.lua
// Written by: TheGamer101
// Description: Module for utility funcitons of the avatar context menu.
]]
--[[
// FileName: ContextMenuGui.lua
// Written by: TheGamer101
// Description: Module for creating the context GUI.
]]
--- CONSTANTS
local STOP_MOVEMENT_ACTION_NAME = "AvatarContextMenuStopInput"
local MAX_THUMBNAIL_WAIT_TIME = 2
local MAX_THUMBNAIL_RETRIES = 4
--- SERVICES
local CoreGuiService = game:GetService("CoreGui")
local PlayersService = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")
--- VARIABLES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local LocalPlayer = PlayersService.LocalPlayer
while not LocalPlayer do
PlayersService.PlayerAdded:wait()
LocalPlayer = PlayersService.LocalPlayer
end
local ContextMenuUtil = {}
ContextMenuUtil.__index = ContextMenuUtil
-- PUBLIC METHODS
function ContextMenuUtil:GetHeadshotForPlayer(player)
if self.HeadShotUrlCache[player] ~= nil and self.HeadShotUrlCache[player] ~= "" then
return self.HeadShotUrlCache[player]
end
if self.HeadShotUrlCache[player] == nil then
-- Mark that we are getting a headshot for this player.
self.HeadShotUrlCache[player] = ""
end
local startTime = tick()
local headshotUrl, isFinal = PlayersService:GetUserThumbnailAsync(player.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size180x180)
if not isFinal then
for i = 0, MAX_THUMBNAIL_RETRIES do
headshotUrl, isFinal = PlayersService:GetUserThumbnailAsync(player.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size180x180)
if isFinal then
break
end
wait(i ^ 2)
end
end
self.HeadShotUrlCache[player] = headshotUrl
return headshotUrl
end
function ContextMenuUtil:HasOrGettingHeadShot(player)
return self.HeadShotUrlCache[player] ~= nil
end
function ContextMenuUtil:FindPlayerFromPart(part)
if part and part.Parent then
local possibleCharacter = part
while possibleCharacter and not possibleCharacter:IsA("Model") do
possibleCharacter = possibleCharacter.Parent
end
if possibleCharacter then
return PlayersService:GetPlayerFromCharacter(possibleCharacter)
end
end
return nil
end
function ContextMenuUtil:GetPlayerPosition(player)
if player.Character then
local hrp = player.Character:FindFirstChild("HumanoidRootPart")
if hrp then
return hrp.Position
end
end
return nil
end
local playerMovementEnabled = true
function ContextMenuUtil:DisablePlayerMovement()
if not playerMovementEnabled then return end
playerMovementEnabled = false
local noOpFunc = function(actionName, actionState)
if actionState == Enum.UserInputState.End then
return Enum.ContextActionResult.Pass
end
return Enum.ContextActionResult.Sink
end
ContextActionService:BindCoreAction(STOP_MOVEMENT_ACTION_NAME, noOpFunc, false,
Enum.PlayerActions.CharacterForward,
Enum.PlayerActions.CharacterBackward,
Enum.PlayerActions.CharacterLeft,
Enum.PlayerActions.CharacterRight,
Enum.PlayerActions.CharacterJump,
Enum.UserInputType.Gamepad1, Enum.UserInputType.Gamepad2, Enum.UserInputType.Gamepad3, Enum.UserInputType.Gamepad4
)
end
function ContextMenuUtil:EnablePlayerMovement()
if playerMovementEnabled then return end
playerMovementEnabled = true
ContextActionService:UnbindCoreAction(STOP_MOVEMENT_ACTION_NAME)
end
function ContextMenuUtil:GetFriendStatus(player)
local success, result = pcall(function()
-- NOTE: Core script only
return LocalPlayer:GetFriendStatus(player)
end)
if success then
return result
else
return Enum.FriendStatus.NotFriend
end
end
local SelectionOverrideObject = Instance.new("ImageLabel")
SelectionOverrideObject.Image = ""
SelectionOverrideObject.BackgroundTransparency = 1
local function MakeDefaultButton(name, size, clickFunc)
local button = Instance.new("ImageButton")
button.Name = name .. "Button"
button.Image = ""
button.ScaleType = Enum.ScaleType.Slice
button.SliceCenter = Rect.new(8,6,46,44)
button.AutoButtonColor = false
button.BackgroundTransparency = 1
button.Size = size
button.ZIndex = 2
button.SelectionImageObject = SelectionOverrideObject
button.BorderSizePixel = 0
local underline = Instance.new("Frame")
underline.Name = "Underline"
underline.BackgroundColor3 = Color3.fromRGB(137,137,137)
underline.AnchorPoint = Vector2.new(0.5,1)
underline.BorderSizePixel = 0
underline.Position = UDim2.new(0.5,0,1,0)
underline.Size = UDim2.new(0.95,0,0,1)
underline.Parent = button
if clickFunc then
button.MouseButton1Click:Connect(function()
clickFunc(UserInputService:GetLastInputType())
end)
end
local function isPointerInput(inputObject)
return inputObject.UserInputType == Enum.UserInputType.MouseMovement or inputObject.UserInputType == Enum.UserInputType.Touch
end
local function selectButton()
button.BackgroundTransparency = 0.5
end
local function deselectButton()
button.BackgroundTransparency = 1
end
button.InputBegan:Connect(function(inputObject)
if button.Selectable and isPointerInput(inputObject) then
selectButton()
inputObject:GetPropertyChangedSignal("UserInputState"):connect(function()
if inputObject.UserInputState == Enum.UserInputState.End then
deselectButton()
end
end)
end
end)
button.InputEnded:Connect(function(inputObject)
if button.Selectable and GuiService.SelectedCoreObject ~= button and isPointerInput(inputObject) then
deselectButton()
end
end)
button.SelectionGained:Connect(function()
selectButton()
end)
button.SelectionLost:Connect(function()
deselectButton()
end)
local guiServiceCon = GuiService.Changed:Connect(function(prop)
if prop ~= "SelectedCoreObject" then return end
if GuiService.SelectedCoreObject == nil or GuiService.SelectedCoreObject ~= button then
deselectButton()
return
end
if button.Selectable then
selectButton()
end
end)
return button
end
local function getViewportSize()
while not workspace.CurrentCamera do
workspace.Changed:wait()
end
-- ViewportSize is initally set to 1, 1 in Camera.cpp constructor.
-- Also check against 0, 0 incase this is changed in the future.
while workspace.CurrentCamera.ViewportSize == Vector2.new(0,0) or
workspace.CurrentCamera.ViewportSize == Vector2.new(1,1) do
workspace.CurrentCamera.Changed:wait()
end
return workspace.CurrentCamera.ViewportSize
end
local function isSmallTouchScreen()
local viewportSize = getViewportSize()
return UserInputService.TouchEnabled and (viewportSize.Y < 500 or viewportSize.X < 700)
end
function ContextMenuUtil:MakeStyledButton(name, text, size, clickFunc)
local button = MakeDefaultButton(name, size, clickFunc)
local textLabel = Instance.new("TextLabel")
textLabel.Name = name .. "TextLabel"
textLabel.BackgroundTransparency = 1
textLabel.BorderSizePixel = 0
textLabel.Size = UDim2.new(1, 0, 1, -8)
textLabel.Position = UDim2.new(0,0,0,0)
textLabel.TextColor3 = Color3.fromRGB(255,255,255)
textLabel.TextYAlignment = Enum.TextYAlignment.Center
textLabel.Font = Enum.Font.SourceSansLight
textLabel.TextSize = 24
textLabel.Text = text
textLabel.TextScaled = true
textLabel.TextWrapped = true
textLabel.ZIndex = 2
textLabel.Parent = button
local constraint = Instance.new("UITextSizeConstraint",textLabel)
if isSmallTouchScreen() then
textLabel.TextSize = 18
elseif GuiService:IsTenFootInterface() then
textLabel.TextSize = 36
end
constraint.MaxTextSize = textLabel.TextSize
return button, textLabel
end
function ContextMenuUtil.new()
local obj = setmetatable({}, ContextMenuUtil)
obj.HeadShotUrlCache = {}
return obj
end
return ContextMenuUtil.new()
@@ -0,0 +1,226 @@
--[[
// FileName: PlayerCarousel.lua
// Written by: darthskrill
// Description: Module for building the UI for the player selection carousel
]]
local PlayerCarousel = {}
PlayerCarousel.__index = PlayerCarousel
-- SERVICES
local GuiService = game:GetService("GuiService")
local CoreGuiService = game:GetService("CoreGui")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")
-- CONSTANTS
local BACKGROUND_SELECTED_COLOR = Color3.fromRGB(0,162,255)
local BACKGROUND_DEFAULT_COLOR = Color3.fromRGB(0,0,0)
local PAGE_LAYOUT_TWEEN_TIME = 0.25
-- VARIABLES
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local CoreGuiModules = RobloxGui:WaitForChild("Modules")
local AvatarMenuModules = CoreGuiModules:WaitForChild("AvatarContextMenu")
local selectedPlayer = nil
local uiPageLayout = nil
local playerChangedEvent = nil
local buttonToPlayerMap = {}
local playerToButtonMap = {}
local function CreateMenuCarousel()
local playerSelection = Instance.new("Frame")
playerSelection.Name = "PlayerCarousel"
playerSelection.AnchorPoint = Vector2.new(0.5, 0.5)
playerSelection.BackgroundTransparency = 1
playerSelection.Position = UDim2.new(0.5,0,0.5,0)
playerSelection.Size = UDim2.new(1, 0, 0.28, 0)
playerSelection.ClipsDescendants = true
local innerFrame = Instance.new("Frame")
innerFrame.Name = "InnerFrame"
innerFrame.AnchorPoint = Vector2.new(0.5, 0.5)
innerFrame.BackgroundTransparency = 1
innerFrame.Position = UDim2.new(0.5, 0, 0.5, 0)
innerFrame.Size = UDim2.new(0.8, 0, 1, 0)
innerFrame.ClipsDescendants = true
innerFrame.Active = true
innerFrame.Parent = playerSelection
selectedPlayer = Instance.new("Frame")
selectedPlayer.Name = "SelectedPlayer"
selectedPlayer.AnchorPoint = Vector2.new(0.5, 0.5)
selectedPlayer.BackgroundTransparency = 1
selectedPlayer.Position = UDim2.new(0.5, 0, 0.5, 0)
selectedPlayer.Size = UDim2.new(0, 100, 1, -10)
selectedPlayer.Parent = innerFrame
uiPageLayout = Instance.new("UIPageLayout")
uiPageLayout.EasingDirection = Enum.EasingDirection.Out
uiPageLayout.EasingStyle = Enum.EasingStyle.Quad
uiPageLayout.Padding = UDim.new(0, 5)
uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
uiPageLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
uiPageLayout.VerticalAlignment = Enum.VerticalAlignment.Center
uiPageLayout.TouchInputEnabled = false
uiPageLayout.SortOrder = Enum.SortOrder.LayoutOrder
local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
aspectRatioConstraint.DominantAxis = Enum.DominantAxis.Height
aspectRatioConstraint.Parent = selectedPlayer
playerChangedEvent = Instance.new("BindableEvent")
playerChangedEvent.Name = "PlayerChanged"
uiPageLayout:GetPropertyChangedSignal("CurrentPage"):Connect(function()
if uiPageLayout.CurrentPage then uiPageLayout.CurrentPage.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR end
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject.Parent == uiPageLayout.Parent then
GuiService.SelectedCoreObject.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
end
GuiService.SelectedCoreObject = uiPageLayout.CurrentPage
if uiPageLayout.CurrentPage then playerChangedEvent:Fire(buttonToPlayerMap[uiPageLayout.CurrentPage]) end
end)
uiPageLayout.Parent = selectedPlayer
local nextButton = Instance.new("ImageButton")
nextButton.Name = "NextButton"
nextButton.Image = "rbxassetid://471630112"
nextButton.BackgroundTransparency = 1
nextButton.AnchorPoint = Vector2.new(1,0.5)
nextButton.Position = UDim2.new(1,-5,0.5,0)
nextButton.Size = UDim2.new(0.3,0,0.3,0)
nextButton.Selectable = false
nextButton.Parent = playerSelection
local aspectRatioConstraint = Instance.new("UIAspectRatioConstraint")
aspectRatioConstraint.DominantAxis = Enum.DominantAxis.Width
aspectRatioConstraint.Parent = nextButton
local prevButton = nextButton:Clone()
prevButton.Name = "PrevButton"
prevButton.AnchorPoint = Vector2.new(0, 0.5)
prevButton.Position = UDim2.new(0, 5, 0.5, 0)
prevButton.Rotation = 180
prevButton.Selectable = false
prevButton.Parent = playerSelection
local function moveChangePage(goToNext)
if goToNext then
uiPageLayout:Next()
else
uiPageLayout:Previous()
end
end
nextButton.MouseButton1Click:Connect(function() moveChangePage(true) end)
prevButton.MouseButton1Click:Connect(function() moveChangePage(false) end)
local defaultChildrenSize = 3 -- aspectRatioConstraint, PageLayout, first button in pagelayout
local function checkButtonVisibility()
local lastInputIsTouch = UserInputService:GetLastInputType() == Enum.UserInputType.Touch
prevButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
nextButton.Visible = not lastInputIsTouch and (#selectedPlayer:GetChildren() > defaultChildrenSize)
end
checkButtonVisibility()
UserInputService.LastInputTypeChanged:Connect(checkButtonVisibility)
return playerSelection
end
function PlayerCarousel:ClearPlayerEntries()
for button, player in pairs(buttonToPlayerMap) do
button:Destroy()
end
buttonToPlayerMap = {}
playerToButtonMap = {}
end
function PlayerCarousel:CreatePlayerEntry(player, distanceToLocalPlayer)
local playerButton = playerToButtonMap[player]
if playerButton then
playerButton.LayoutOrder = distanceToLocalPlayer
return
end
local button = Instance.new("ImageButton")
button.Name = player.Name
button.BorderSizePixel = 0
button.LayoutOrder = distanceToLocalPlayer
button.BackgroundColor3 = Color3.fromRGB(0,0,0)
button.BackgroundTransparency = 0
button.Size = UDim2.new(1, 0, 1, 0)
button.SelectionLost:connect(function() button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR end)
button.SelectionGained:connect(function() button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR end)
local tweenStyle = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, true)
local buttonLoadingTween = TweenService:Create(button, tweenStyle, {BackgroundTransparency = 1, BackgroundColor3 = Color3.fromRGB(255,255,255)})
buttonLoadingTween:Play()
buttonToPlayerMap[button] = player
playerToButtonMap[player] = button
button.MouseButton1Click:Connect(function()
uiPageLayout:JumpTo(button)
end)
button.Parent = selectedPlayer
spawn(function()
local ContextMenuUtil = require(AvatarMenuModules:WaitForChild("ContextMenuUtil"))
button.Image = ContextMenuUtil:GetHeadshotForPlayer(player)
buttonLoadingTween:Cancel()
buttonLoadingTween = nil
button.BackgroundTransparency = 0
if button == GuiService.SelectedCoreObject then
button.BackgroundColor3 = BACKGROUND_SELECTED_COLOR
else
button.BackgroundColor3 = BACKGROUND_DEFAULT_COLOR
end
end)
end
function PlayerCarousel:SwitchToPlayerEntry(player, dontTween)
if not player then return end
local button = playerToButtonMap[player]
if not button then
self:CreatePlayerEntry(player, 0)
button = playerToButtonMap[player]
end
if dontTween then
uiPageLayout.TweenTime = 0
end
uiPageLayout:JumpTo(button)
spawn(function()
uiPageLayout.TweenTime = PAGE_LAYOUT_TWEEN_TIME
end)
end
function PlayerCarousel:OffsetPlayerEntry(offset)
if offset == 0 then return end
if offset > 0 then
uiPageLayout:Next()
else
uiPageLayout:Previous()
end
end
function PlayerCarousel:GetSelectedPlayer()
return buttonToPlayerMap[uiPageLayout.CurrentPage]
end
function PlayerCarousel.new()
local obj = setmetatable({}, PlayerCarousel)
obj.rbxGui = CreateMenuCarousel()
obj.PlayerChanged = playerChangedEvent.Event
return obj
end
return PlayerCarousel.new()
@@ -0,0 +1,102 @@
--[[
// FileName: SelectedCharacterIndicator.lua
// Written by: TheGamer101
// Description: Module for rendering an effect for the selected character .
]]
local SelectedCharacterIndicator = {}
SelectedCharacterIndicator.__index = SelectedCharacterIndicator
local RunService = game:GetService("RunService")
local RENDER_ARROW_CONTEXT_ACTION = "ContextActionMenuRenderArrow"
local CurrentCamera = workspace.CurrentCamera
function ApplyArrow(character)
local baseModel = Instance.new("Model")
baseModel.Name = "ContextMenuArrow"
baseModel.Parent = CurrentCamera
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid == nil then
humanoid = character:WaitForChild("Humanoid", 15)
if humanoid == nil then
return
end
end
local torso = character:WaitForChild("HumanoidRootPart")
local arrowPart = game:GetService("InsertService"):LoadLocalAsset("rbxasset://models/AvatarContextMenu/AvatarContextArrow.rbxm")
arrowPart.Anchored = true
arrowPart.Transparency = 0
arrowPart.CanCollide = false
arrowPart.Parent = baseModel
local arrowTween = game:GetService("TweenService"):Create(arrowPart,
TweenInfo.new(4, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1, false),
{Orientation=Vector3.new(0,360,180)})
arrowTween:Play()
local function update()
arrowPart.Position = torso.Position + Vector3.new(0, 5, 0)
end
local isKilled = false
local function kill()
if isKilled then
return
end
isKilled = true
baseModel:Destroy()
arrowTween:Destroy()
RunService:UnbindFromRenderStep(RENDER_ARROW_CONTEXT_ACTION)
end
humanoid.Died:Connect(kill)
character.AncestryChanged:Connect(kill)
RunService:BindToRenderStep(RENDER_ARROW_CONTEXT_ACTION, Enum.RenderPriority.Camera.Value + 1 , update)
baseModel.Parent = CurrentCamera
return kill
end
function SelectedCharacterIndicator:ChangeSelectedPlayer(selectedPlayer)
coroutine.wrap(function()
if self.SelectedPlayer then
self.SelectedPlayer = nil
self.CharacterAddedConn:Disconnect()
self.CharacterAddedConn = nil
if self.KillOldRenderFunction then
self.KillOldRenderFunction()
self.KillOldRenderFunction = nil
end
end
if selectedPlayer then
self.SelectedPlayer = selectedPlayer
self.CharacterAddedConn = selectedPlayer.CharacterAdded:Connect(function(character)
if self.KillOldRenderFunction then
self.KillOldRenderFunction()
end
self.KillOldRenderFunction = ApplyArrow(character)
end)
if selectedPlayer.Character then
self.KillOldRenderFunction = ApplyArrow(selectedPlayer.Character)
end
end
end)()
end
function SelectedCharacterIndicator.new()
local obj = setmetatable({}, SelectedCharacterIndicator)
obj.KillOldRenderFunction = nil
obj.SelectedPlayer = nil
obj.CharacterAddedConn = nil
return obj
end
return SelectedCharacterIndicator.new()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,521 @@
--BackpackScript3D: VR port of backpack interface using a 3D panel
--written by 0xBAADF00D
local ICON_SIZE = 48
local ICON_SPACING = 52
local PIXELS_PER_STUD = 64
local SLOT_BORDER_SIZE = 0
local SLOT_BORDER_SELECTED_SIZE = 4
local SLOT_BORDER_COLOR = Color3.new(90/255, 142/255, 233/255)
local SLOT_BACKGROUND_COLOR = Color3.new(0.2, 0.2, 0.2)
local SLOT_HOVER_BACKGROUND_COLOR = Color3.new(90/255, 90/255, 90/255)
local HOPPERBIN_ANGLE = math.rad(-45)
local HOPPERBIN_ROTATION = CFrame.Angles(HOPPERBIN_ANGLE, 0, 0)
local HOPPERBIN_OFFSET = Vector3.new(0, 0, -5)
local HEALTHBAR_SPACE = 12
local HEALTHBAR_WIDTH = 82
local HEALTHBAR_HEIGHT = 5
local NAME_SPACE = 14
local Tools = {}
local ToolsList = {}
local slotIcons = {}
local BackpackScript = {}
local topbarEnabled = false
local player = game:GetService("Players").LocalPlayer
local currentHumanoid = nil
local CoreGui = game:GetService('CoreGui')
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local Util = require(RobloxGui.Modules.Settings.Utility)
local ContextActionService = game:GetService("ContextActionService")
local BackpackPanel = Panel3D.Get("Backpack")
BackpackPanel:ResizeStuds(5, 2)
BackpackPanel:SetType(Panel3D.Type.Fixed, { CFrame = CFrame.new(0, 0, -5) })
BackpackPanel:SetVisible(true)
local toolsFrame = Instance.new("TextButton", BackpackPanel:GetGUI()) --prevent clicks falling through in case you have a rocket launcher and blow yourself up
toolsFrame.Text = ""
toolsFrame.Size = UDim2.new(1, 0, 0, ICON_SIZE)
toolsFrame.BackgroundTransparency = 1
toolsFrame.Selectable = false
local insetAdjustY = toolsFrame.AbsolutePosition.Y
toolsFrame.Position = UDim2.new(0, 0, 0, HEALTHBAR_SPACE + NAME_SPACE)
--Healthbar color function stolen from Topbar.lua
local HEALTH_BACKGROUND_COLOR = Color3.new(228/255, 236/255, 246/255)
local HEALTH_RED_COLOR = Color3.new(255/255, 28/255, 0/255)
local HEALTH_YELLOW_COLOR = Color3.new(250/255, 235/255, 0)
local HEALTH_GREEN_COLOR = Color3.new(27/255, 252/255, 107/255)
local healthbarBack = Instance.new("ImageLabel", BackpackPanel:GetGUI())
healthbarBack.ImageColor3 = HEALTH_BACKGROUND_COLOR
healthbarBack.BackgroundTransparency = 1
healthbarBack.ScaleType = Enum.ScaleType.Slice
healthbarBack.SliceCenter = Rect.new(10, 10, 10, 10)
healthbarBack.Name = "HealthbarContainer"
healthbarBack.Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png"
local healthbarFront = Instance.new("ImageLabel", healthbarBack)
healthbarFront.ImageColor3 = HEALTH_GREEN_COLOR
healthbarFront.BackgroundTransparency = 1
healthbarFront.ScaleType = Enum.ScaleType.Slice
healthbarFront.SliceCenter = Rect.new(10, 10, 10, 10)
healthbarFront.Size = UDim2.new(1, 0, 1, 0)
healthbarFront.Position = UDim2.new(0, 0, 0, 0)
healthbarFront.Name = "HealthbarFill"
healthbarFront.Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png"
local playerName = Instance.new("TextLabel", BackpackPanel:GetGUI())
playerName.Name = "PlayerName"
playerName.BackgroundTransparency = 1
playerName.TextColor3 = Color3.new(1, 1, 1)
playerName.Text = player.Name
playerName.Font = Enum.Font.SourceSansBold
playerName.FontSize = Enum.FontSize.Size12
playerName.TextXAlignment = Enum.TextXAlignment.Left
playerName.Size = UDim2.new(1, 0, 0, NAME_SPACE)
BackpackScript.ToolAddedEvent = Instance.new("BindableEvent")
local healthColorToPosition = {
[Vector3.new(HEALTH_RED_COLOR.r, HEALTH_RED_COLOR.g, HEALTH_RED_COLOR.b)] = 0.1;
[Vector3.new(HEALTH_YELLOW_COLOR.r, HEALTH_YELLOW_COLOR.g, HEALTH_YELLOW_COLOR.b)] = 0.5;
[Vector3.new(HEALTH_GREEN_COLOR.r, HEALTH_GREEN_COLOR.g, HEALTH_GREEN_COLOR.b)] = 0.8;
}
local min = 0.1
local minColor = HEALTH_RED_COLOR
local max = 0.8
local maxColor = HEALTH_GREEN_COLOR
local function HealthbarColorTransferFunction(healthPercent)
if healthPercent < min then
return minColor
elseif healthPercent > max then
return maxColor
end
-- Shepard's Interpolation
local numeratorSum = Vector3.new(0,0,0)
local denominatorSum = 0
for colorSampleValue, samplePoint in pairs(healthColorToPosition) do
local distance = healthPercent - samplePoint
if distance == 0 then
-- If we are exactly on an existing sample value then we don't need to interpolate
return Color3.new(colorSampleValue.x, colorSampleValue.y, colorSampleValue.z)
else
local wi = 1 / (distance*distance)
numeratorSum = numeratorSum + wi * colorSampleValue
denominatorSum = denominatorSum + wi
end
end
local result = numeratorSum / denominatorSum
return Color3.new(result.x, result.y, result.z)
end
---
local backpackEnabled = true
local healthbarEnabled = true
local function UpdateLayout()
local width, height = 100, 100
local borderSize = (ICON_SPACING - ICON_SIZE) / 2
local x = borderSize
local y = 0
for _, tool in ipairs(ToolsList) do
local slot = Tools[tool]
if slot then
slot.icon.Position = UDim2.new(0, x, 0, y)
x = x + ICON_SPACING
end
end
if #ToolsList == 0 then
width = HEALTHBAR_WIDTH
height = HEALTHBAR_SPACE + NAME_SPACE
BackpackPanel.showCursor = false
else
width = #ToolsList * ICON_SPACING
height = ICON_SIZE + HEALTHBAR_SPACE + NAME_SPACE
BackpackPanel.showCursor = true
end
BackpackPanel:ResizePixels(width, height)
playerName.Position = UDim2.new(0, borderSize, 0, 0)
healthbarBack.Position = UDim2.new(0, borderSize, 0, NAME_SPACE + (HEALTHBAR_SPACE - HEALTHBAR_HEIGHT) / 2)
healthbarBack.Size = UDim2.new(0, HEALTHBAR_WIDTH, 0, HEALTHBAR_HEIGHT)
end
local function UpdateHealth(humanoid)
local percentHealth = humanoid.Health / humanoid.MaxHealth
if percentHealth ~= percentHealth then
percentHealth = 1
end
healthbarFront.BackgroundColor3 = HealthbarColorTransferFunction(percentHealth)
healthbarFront.Size = UDim2.new(percentHealth, 0, 1, 0)
end
local function SetTransparency(transparency)
for i, v in pairs(Tools) do
v.bg.ImageTransparency = transparency
v.image.ImageTransparency = transparency
v.text.TextTransparency = transparency
end
playerName.TextTransparency = transparency
healthbarBack.ImageTransparency = transparency
healthbarFront.ImageTransparency = transparency
end
local function OnHotbarEquipPrimary(actionName, state, obj)
if state ~= Enum.UserInputState.Begin then
return
end
for tool, slot in pairs(Tools) do
if slot.hovered then
slot.OnClick()
return
end
end
end
local function EnableHotbarInput(enable)
if not backpackEnabled then
enable = false
end
if not currentHumanoid then
return
end
if enable then
ContextActionService:BindCoreAction("HotbarEquipPrimary", OnHotbarEquipPrimary, false, Enum.KeyCode.ButtonA, Enum.KeyCode.ButtonR2, Enum.UserInputType.MouseButton1)
else
ContextActionService:UnbindCoreAction("HotbarEquipPrimary")
end
end
local function AddTool(tool)
if Tools[tool] then
return
end
local slot = {}
Tools[tool] = slot
table.insert(ToolsList, tool)
slot.hovered = false
slot.tool = tool
slot.icon = Instance.new("TextButton", toolsFrame)
slot.icon.Text = ""
slot.icon.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE)
slot.icon.BackgroundColor3 = Color3.new(0, 0, 0)
slot.icon.Selectable = true
slot.icon.BackgroundTransparency = 1
slotIcons[tool] = slot.icon
slot.bg = Instance.new("ImageLabel", slot.icon)
slot.bg.Position = UDim2.new(0, -1, 0, -1)
slot.bg.Size = UDim2.new(1, 2, 1, 2)
slot.bg.Image = "rbxasset://textures/ui/VR/rectBackground.png"
slot.bg.ScaleType = Enum.ScaleType.Slice
slot.bg.SliceCenter = Rect.new(10, 10, 10, 10)
slot.bg.BackgroundTransparency = 1
slot.image = Instance.new("ImageLabel", slot.icon)
slot.image.Position = UDim2.new(0, 1, 0, 1)
slot.image.Size = UDim2.new(1, -2, 1, -2)
slot.image.BackgroundTransparency = 1
slot.image.Selectable = false
slot.text = Instance.new("TextLabel", slot.icon)
slot.text.Position = UDim2.new(0, 1, 0, 1)
slot.text.Size = UDim2.new(1, -2, 1, -2)
slot.text.BackgroundTransparency = 1
slot.text.TextColor3 = Color3.new(1, 1, 1)
slot.text.Font = Enum.Font.SourceSans
slot.text.FontSize = Enum.FontSize.Size12
slot.text.ClipsDescendants = true
slot.text.Selectable = false
local selectionObject = Util:Create'ImageLabel'
{
Name = 'SelectionObject';
Size = UDim2.new(1,0,1,0);
BackgroundTransparency = 1;
Image = "rbxasset://textures/ui/Keyboard/key_selection_9slice.png";
ImageTransparency = 0;
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(12,12,52,52);
BorderSizePixel = 0;
}
slot.icon.SelectionImageObject = selectionObject
local function updateToolData()
slot.image.Image = tool.TextureId
slot.text.Text = tool.TextureId == "" and tool.Name or ""
end
updateToolData()
slot.OnClick = function()
if not player.Character then return end
local humanoid = player.Character:FindFirstChild("Humanoid")
if not humanoid then return end
local inBackpack = tool.Parent == player.Backpack
humanoid:UnequipTools()
if inBackpack then
humanoid:EquipTool(tool)
end
end
slot.icon.MouseButton1Click:connect(slot.OnClick)
slot.OnEnter = function()
slot.hovered = true
end
slot.OnLeave = function()
slot.hovered = false
end
-- slot.icon.MouseEnter:connect(slot.OnEnter)
-- slot.icon.MouseLeave:connect(slot.OnLeave)
tool.Changed:connect(function(prop)
if prop == "Parent" then
if tool.Parent == player:FindFirstChild("Backpack") then
slot.bg.Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE) --temporary hold-over until new backpack design comes along (can't use border with this antialiased frame stand-in)
slot.bg.Position = UDim2.new(0, 0, 0, 0)
elseif tool.Parent == player.Character then
slot.bg.Size = UDim2.new(0, ICON_SIZE + 8, 0, ICON_SIZE + 8)
slot.bg.Position = UDim2.new(0, -4, 0, -4)
end
elseif prop == "TextureId" or prop == "Name" then
updateToolData()
end
end)
UpdateLayout()
BackpackScript.ToolAddedEvent:Fire(tool)
end
local humanoidChangedEvent = nil
local humanoidAncestryChangedEvent = nil
local function RegisterHumanoid(humanoid)
currentHumanoid = humanoid
if humanoidChangedEvent then
humanoidChangedEvent:disconnect()
humanoidChangedEvent = nil
end
if humanoidAncestryChangedEvent then
humanoidAncestryChangedEvent:disconnect()
humanoidAncestryChangedEvent = nil
end
if humanoid then
humanoidChangedEvent = humanoid.HealthChanged:connect(function() UpdateHealth(humanoid) end)
humanoidAncestryChangedEvent = humanoid.AncestryChanged:connect(function(child, parent)
if child == humanoid and parent ~= player.Character then
RegisterHumanoid(nil)
end
end)
UpdateHealth(humanoid)
end
end
local function OnChildAdded(child)
if child:IsA("Tool") or child:IsA("HopperBin") then
AddTool(child)
end
if child:IsA("Humanoid") and child.Parent == player.Character then
RegisterHumanoid(child)
end
end
local function RemoveTool(tool)
if not Tools[tool] then
return
end
Tools[tool].icon:Destroy()
for i, v in ipairs(ToolsList) do
if v == tool then
table.remove(ToolsList, i)
break
end
end
Tools[tool] = nil
slotIcons[tool] = nil
UpdateLayout()
end
local function OnChildRemoved(child)
if child:IsA("Tool") or child:IsA("HopperBin") then
if Tools[child] then
if child.Parent ~= player:FindFirstChild("Backpack") and child.Parent ~= player.Character then
RemoveTool(child)
end
end
end
end
local function OnCharacterAdded(character)
local backpack = player:WaitForChild("Backpack")
for i, v in ipairs(character:GetChildren()) do
if v:IsA("Humanoid") then
RegisterHumanoid(v)
break
end
end
for tool, v in pairs(Tools) do
RemoveTool(tool)
end
Tools = {}
ToolsList = {}
character.ChildAdded:connect(OnChildAdded)
character.ChildRemoved:connect(OnChildRemoved)
for i, v in ipairs(backpack:GetChildren()) do
OnChildAdded(v)
end
backpack.ChildAdded:connect(OnChildAdded)
backpack.ChildRemoved:connect(OnChildRemoved)
end
player.CharacterAdded:connect(OnCharacterAdded)
if player.Character then
spawn(function() OnCharacterAdded(player.Character) end)
end
local function OnHotbarEquip(actionName, state, obj)
if not backpackEnabled then
return
end
local character = player.Character
if not character then
return
end
if not currentHumanoid then
return
end
if state ~= Enum.UserInputState.Begin then
return
end
if #ToolsList == 0 then
return
end
local current = 0
for i, v in pairs(ToolsList) do
if v.Parent == character then
current = i
end
end
currentHumanoid:UnequipTools()
if obj.KeyCode == Enum.KeyCode.ButtonR1 then
current = current + 1
if current > #ToolsList then
current = 1
end
else
current = current - 1
if current < 1 then
current = #ToolsList
end
end
currentHumanoid:EquipTool(ToolsList[current])
end
local function OnCoreGuiChanged(coreGuiType, enabled)
-- Check for enabling/disabling the whole thing
if coreGuiType == Enum.CoreGuiType.Backpack or coreGuiType == Enum.CoreGuiType.All then
backpackEnabled = enabled
UpdateLayout()
if enabled then
ContextActionService:BindCoreAction("HotbarEquip2", OnHotbarEquip, false, Enum.KeyCode.ButtonL1, Enum.KeyCode.ButtonR1)
toolsFrame.Parent = BackpackPanel:GetGUI()
else
ContextActionService:UnbindCoreAction("HotbarEquip2")
toolsFrame.Parent = nil
end
end
if coreGuiType == Enum.CoreGuiType.Health or coreGuiType == Enum.CoreGuiType.All then
healthbarEnabled = enabled
UpdateLayout()
if enabled then
healthbarBack.Parent = BackpackPanel:GetGUI()
else
healthbarBack.Parent = nil
end
end
end
local StarterGui = game:GetService("StarterGui")
StarterGui.CoreGuiChangedSignal:connect(OnCoreGuiChanged)
OnCoreGuiChanged(Enum.CoreGuiType.Backpack, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Backpack))
OnCoreGuiChanged(Enum.CoreGuiType.Backpack, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.All))
OnCoreGuiChanged(Enum.CoreGuiType.Health, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Health))
OnCoreGuiChanged(Enum.CoreGuiType.Health, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.All))
local panelLocalCF = CFrame.Angles(math.rad(-5), 0, 0) * CFrame.new(0, 1.75, 0) * CFrame.Angles(math.rad(-5), 0, 0)
function BackpackPanel:PreUpdate(cameraCF, cameraRenderCF, userHeadCF, lookRay)
--the backpack panel needs to go in front of the user when they look at it.
--if they aren't looking, we should be updating self.localCF
local topbarPanel = Panel3D.Get("Topbar3D")
local panelOriginCF = topbarPanel.localCF or CFrame.new()
self.localCF = panelOriginCF * panelLocalCF
end
function BackpackPanel:OnUpdate()
SetTransparency(self.transparency)
local hovered, tool = BackpackPanel:FindHoveredGuiElement(slotIcons)
if hovered and tool then
local slot = Tools[tool]
if not slot.hovered then
slot.OnEnter()
end
for i, v in pairs(Tools) do
if v.hovered and v ~= slot then
v.OnLeave()
end
end
end
end
function BackpackPanel:OnMouseEnter(x, y)
EnableHotbarInput(true)
end
function BackpackPanel:OnMouseLeave(x, y)
EnableHotbarInput(false)
end
local VRHub = require(RobloxGui.Modules.VR.VRHub)
VRHub.ModuleOpened.Event:connect(function(moduleName)
local module = VRHub:GetModule(moduleName)
if module.VRIsExclusive then
BackpackPanel:SetVisible(false)
end
end)
VRHub.ModuleClosed.Event:connect(function(moduleName)
BackpackPanel:SetVisible(true)
end)
BackpackPanel:LinkTo("Topbar3D")
return BackpackScript
@@ -0,0 +1,19 @@
local BusinessLogic = {}
function BusinessLogic.GetVisibleAgeForPlayer(player)
local accountTypeText = "Account: <13"
if player and not player:GetUnder13() then
accountTypeText = "Account: 13+"
end
return accountTypeText
end
return BusinessLogic
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,191 @@
--[[
// FileName: ChatSelector.lua
// Written by: Xsitsu
// Description: Code for determining which chat version to use in game.
]]
local FORCE_IS_CONSOLE = false
local FORCE_IS_VR = false
local CoreGuiService = game:GetService("CoreGui")
local RobloxGui = CoreGuiService:WaitForChild("RobloxGui")
local Modules = RobloxGui:WaitForChild("Modules")
local Common = Modules:WaitForChild("Common")
local StarterGui = game:GetService("StarterGui")
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Util = require(RobloxGui.Modules.ChatUtil)
local ClassicChatEnabled = Players.ClassicChat
local BubbleChatEnabled = Players.BubbleChat
local useModule = nil
local state = {Visible = true}
local interface = {}
do
function interface:ToggleVisibility()
if (useModule) then
useModule:ToggleVisibility()
else
state.Visible = not state.Visible
end
end
function interface:SetVisible(visible)
if (useModule) then
useModule:SetVisible(visible)
else
state.Visible = visible
end
end
function interface:FocusChatBar()
if (useModule) then
useModule:FocusChatBar()
end
end
function interface:GetVisibility()
if (useModule) then
return useModule:GetVisibility()
else
return state.Visible
end
end
function interface:GetMessageCount()
if (useModule) then
return useModule:GetMessageCount()
else
return 0
end
end
function interface:TopbarEnabledChanged(...)
if (useModule) then
return useModule:TopbarEnabledChanged(...)
end
end
function interface:IsFocused(useWasFocused)
if (useModule) then
return useModule:IsFocused(useWasFocused)
else
return false
end
end
function interface:ClassicChatEnabled()
if useModule then
return useModule:ClassicChatEnabled()
else
return ClassicChatEnabled
end
end
function interface:IsBubbleChatOnly()
if useModule then
return useModule:IsBubbleChatOnly()
end
return BubbleChatEnabled and not ClassicChatEnabled
end
function interface:IsDisabled()
if useModule then
return useModule:IsDisabled()
end
return not (BubbleChatEnabled or ClassicChatEnabled)
end
interface.ChatBarFocusChanged = Util.Signal()
interface.VisibilityStateChanged = Util.Signal()
interface.MessagesChanged = Util.Signal()
-- Signals that are called when we get information on if Bubble Chat and Classic chat are enabled from the chat.
interface.BubbleChatOnlySet = Util.Signal()
interface.ChatDisabled = Util.Signal()
end
local StopQueueingSystemMessages = false
local MakeSystemMessageQueue = {}
local function MakeSystemMessageQueueingFunction(data)
if (StopQueueingSystemMessages) then return end
table.insert(MakeSystemMessageQueue, data)
end
local function NonFunc() end
StarterGui:RegisterSetCore("ChatMakeSystemMessage", MakeSystemMessageQueueingFunction)
StarterGui:RegisterSetCore("ChatWindowPosition", NonFunc)
StarterGui:RegisterGetCore("ChatWindowPosition", NonFunc)
StarterGui:RegisterSetCore("ChatWindowSize", NonFunc)
StarterGui:RegisterGetCore("ChatWindowSize", NonFunc)
StarterGui:RegisterSetCore("ChatBarDisabled", NonFunc)
StarterGui:RegisterGetCore("ChatBarDisabled", NonFunc)
StarterGui:RegisterGetCore("ChatActive", function()
return interface:GetVisibility()
end)
StarterGui:RegisterSetCore("ChatActive", function(visible)
return interface:SetVisible(visible)
end)
local function ConnectSignals(useModule, interface, sigName)
--// "MessagesChanged" event is not created for Studio Start Server
if (useModule[sigName]) then
useModule[sigName]:connect(function(...) interface[sigName]:fire(...) end)
end
end
local isConsole = GuiService:IsTenFootInterface() or FORCE_IS_CONSOLE
local isVR = UserInputService.VREnabled or FORCE_IS_VR
if ( not isConsole and not isVR ) then
spawn(function()
useModule = require(RobloxGui.Modules.NewChat)
ConnectSignals(useModule, interface, "ChatBarFocusChanged")
ConnectSignals(useModule, interface, "VisibilityStateChanged")
ConnectSignals(useModule, interface, "BubbleChatOnlySet")
ConnectSignals(useModule, interface, "ChatDisabled")
while Players.LocalPlayer == nil do Players.ChildAdded:wait() end
local LocalPlayer = Players.LocalPlayer
ConnectSignals(useModule, interface, "MessagesChanged")
-- Retained for legacy reasons, no longer used by the chat scripts.
StarterGui:RegisterGetCore("UseNewLuaChat", function() return true end)
useModule:SetVisible(state.Visible)
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Chat, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Chat))
StopQueueingSystemMessages = true
for i, messageData in pairs(MakeSystemMessageQueue) do
pcall(function() StarterGui:SetCore("ChatMakeSystemMessage", messageData) end)
end
end)
elseif not isConsole then
useModule = require(RobloxGui.Modules.Chat)
ConnectSignals(useModule, interface, "ChatBarFocusChanged")
ConnectSignals(useModule, interface, "VisibilityStateChanged")
while Players.LocalPlayer == nil do Players.ChildAdded:wait() end
local LocalPlayer = Players.LocalPlayer
if (LocalPlayer.ChatMode == Enum.ChatMode.TextAndMenu or RunService:IsStudio()) then
ConnectSignals(useModule, interface, "MessagesChanged")
end
StarterGui:RegisterGetCore("UseNewLuaChat", function() return false end)
end
return interface
@@ -0,0 +1,34 @@
local Util = {}
do
function Util.Signal()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
end
return Util
@@ -0,0 +1,61 @@
--[[
Filename: CommonUtil.lua
Written by: dbanks
Description: Common work.
--]]
--[[ Classes ]]--
local CommonUtil = {}
-- Concatenate these two tables, return result.
function CommonUtil.TableConcat(t1,t2)
for i=1,#t2 do
t1[#t1+1] = t2[i]
end
return t1
end
-- Instances have a "Name" field. Sort
-- by that name,
function CommonUtil.SortByName(items)
local function compareInstanceNames(i1, i2)
return (i1.Name < i2.Name)
end
table.sort(items, compareInstanceNames)
return items
end
-- Provides a nice syntax for creating Roblox instances.
-- Example:
-- local newPart = Utility.Create("Part") {
-- Parent = workspace,
-- Anchored = true,
--
-- --Create a SpecialMesh as a child of this part too
-- Utility.Create("SpecialMesh") {
-- MeshId = "rbxassetid://...",
-- Scale = Vector3.new(0.5, 0.2, 10)
-- }
-- }
function CommonUtil.Create(instanceType)
return function(data)
local obj = Instance.new(instanceType)
local parent = nil
for k, v in pairs(data) do
if type(k) == 'number' then
v.Parent = obj
elseif k == 'Parent' then
parent = v
else
obj[k] = v
end
end
if parent then
obj.Parent = parent
end
return obj
end
end
return CommonUtil
@@ -0,0 +1,38 @@
-- universal design constants for in-game ui style
local Constants = {
COLORS = {
SLATE = Color3.fromRGB(35, 37, 39),
FLINT = Color3.fromRGB(57, 59, 61),
GRAPHITE = Color3.fromRGB(101, 102, 104),
PUMICE = Color3.fromRGB(189, 190, 190),
WHITE = Color3.fromRGB(255, 255, 255),
},
ERROR_PROMPT_HEIGHT = {
Default = 236,
XBox = 180,
},
ERROR_PROMPT_MIN_WIDTH = {
Default = 320,
XBox = 400,
},
ERROR_PROMPT_MAX_WIDTH = {
Default = 400,
XBox = 400,
},
ERROR_TITLE_FRAME_HEIGHT = {
Default = 50,
},
SPLIT_LINE_THICKNESS = 1,
BUTTON_CELL_PADDING = 10,
BUTTON_HEIGHT = 36,
SIDE_PADDING = 20,
LAYOUT_PADDING = 20,
SIDE_MARGIN = 20, -- When resizing according to screen size, reserve with side margins
PRIMARY_BUTTON_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/PrimaryButton.png",
SECONDARY_BUTTON_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/SecondaryButton.png",
SHIMMER_TEXTURE = "rbxasset://textures/ui/LuaApp/graphic/shimmer.png",
OVERLAY_TEXTURE = "rbxasset://textures/ui/ErrorPrompt/ShimmerOverlay.png",
}
return Constants
@@ -0,0 +1,33 @@
return function(className, defaultParent)
return function(propertyList)
local object = Instance.new(className)
local parent = nil
for index, value in next, propertyList do
if typeof(index) == 'string' then
if index == 'Parent' then
parent = value
else
object[index] = value
end
else
local valueType = typeof(value)
if valueType == 'function' then
value(object)
elseif valueType == 'Instance' then
value.Parent = object
end
end
end
if parent then
object.Parent = parent
end
if object.Parent == nil then
object.Parent = defaultParent
end
return object
end
end
@@ -0,0 +1,51 @@
-- // FileName: ObjectPool.lua
-- // Written by: TheGamer101
-- // Description: An object pool class used to avoid unnecessarily instantiating Instances.
local module = {}
--////////////////////////////// Include
--//////////////////////////////////////
local modulesFolder = script.Parent
--////////////////////////////// Methods
--//////////////////////////////////////
local methods = {}
methods.__index = methods
function methods:GetInstance(className)
if self.InstancePoolsByClass[className] == nil then
self.InstancePoolsByClass[className] = {}
end
local availableInstances = #self.InstancePoolsByClass[className]
if availableInstances > 0 then
local instance = self.InstancePoolsByClass[className][availableInstances]
table.remove(self.InstancePoolsByClass[className])
return instance
end
return Instance.new(className)
end
function methods:ReturnInstance(instance)
if self.InstancePoolsByClass[instance.ClassName] == nil then
self.InstancePoolsByClass[instance.ClassName] = {}
end
if #self.InstancePoolsByClass[instance.ClassName] < self.PoolSizePerType then
table.insert(self.InstancePoolsByClass[instance.ClassName], instance)
else
instance:Destroy()
end
end
--///////////////////////// Constructors
--//////////////////////////////////////
function module.new(poolSizePerType)
local obj = setmetatable({}, methods)
obj.InstancePoolsByClass = {}
obj.Name = "ObjectPool"
obj.PoolSizePerType = poolSizePerType
return obj
end
return module
@@ -0,0 +1,68 @@
--[[
A helper function to define a Rodux action creator with an associated name.
Normally when creating a Rodux action, you can just create a function:
return function(value)
return {
type = "MyAction",
value = value,
}
end
And then when you check for it in your reducer, you either use a constant,
or type out the string name:
if action.type == "MyAction" then
-- change some state
end
Typos here are a remarkably common bug. We also have the issue that there's
no link between reducers and the actions that they respond to!
`Action` (this helper) provides a utility that makes this a bit cleaner.
Instead, define your Rodux action like this:
return Action("MyAction", function(value)
return {
value = value,
}
end)
We no longer need to add the `type` field manually.
Additionally, the returned action creator now has a 'name' property that can
be checked by your reducer:
local MyAction = require(Reducers.MyAction)
...
if action.type == MyAction.name then
-- change some state!
end
Now we have a clear link between our reducers and the actions they use, and
if we ever typo a name, we'll get a warning in LuaCheck as well as an error
at runtime!
]]
return function(name, fn)
assert(type(name) == "string", "A name must be provided to create an Action")
assert(type(fn) == "function", "A function must be provided to create an Action")
return setmetatable({
name = name,
}, {
__call = function(self, ...)
local result = fn(...)
assert(type(result) == "table", "An action must return a table")
result.type = name
return result
end
})
end
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("ActionBindingsUpdateSearchFilter", function(searchTerm)
return {
searchTerm = searchTerm
}
end)
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("ChangeDevConsoleSize", function(newSize)
return {
newSize = newSize
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ClientLogUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ClientMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ClientNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ClientScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("DataStoresUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerJobsUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerLogUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,9 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("ServerStatsUpdateSearchFilter", function(searchTerm, filterTypes)
return {
searchTerm = searchTerm,
filterTypes = filterTypes
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("SetActiveTab", function(tabListIndex, isClientView)
return {
newTabIndex = tabListIndex,
isClientView = isClientView
}
end)
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("SetDevConsoleMinimized", function(minimize)
return {
isMinimized = minimize
}
end)
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("SetDevConsolePosition", function(pos)
return {
position = pos
}
end)
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("SetDevConsoleVisibility", function(visibility)
return {
isVisible = visibility
}
end)
@@ -0,0 +1,8 @@
local Action = require(script.Parent.Parent.Action)
return Action("SetTabList", function(tabList, initIndex)
return {
tabList = tabList,
initIndex = initIndex,
}
end)
@@ -0,0 +1,7 @@
local Action = require(script.Parent.Parent.Action)
return Action("UpdateAveragePing", function(newAveragePing)
return {
AveragePing = newAveragePing
}
end)
@@ -0,0 +1,151 @@
local CircularBuffer = {}
CircularBuffer.__index = CircularBuffer
function CircularBuffer.new(size)
assert(size, "Cannot initialize CircularBuffer with nil")
assert(size > 0, "Cannot initialize CircularBuffer to size < 1")
local self = {}
setmetatable(self, CircularBuffer)
self._data = {}
self._backIndex = 0
self._maxSize = size
return self
end
function CircularBuffer:reset()
self._data = {}
self._backIndex = 0
end
function CircularBuffer:getSize()
return #self._data
end
function CircularBuffer:getMaxSize()
return self._maxSize
end
function CircularBuffer:setSize(newSize)
assert(newSize, "Cannot set CircularBuffer with nil")
assert(newSize > 0, "Cannot set CircularBuffer to size < 1")
if newSize == self._maxSize then
return
end
local it = self:iterator()
local msg = it:next()
local sorted = {}
local ind = 0
while msg and ind < newSize do
local nextInd = ind + 1
sorted[nextInd] = {
entry = msg
}
if sorted[ind] then
sorted[ind]._next = sorted[nextInd]
end
ind = nextInd
msg = it:next()
end
self._data = sorted
self._backIndex = ind
self._maxSize = newSize
end
function CircularBuffer:getFrontIndex()
local front = self._backIndex + 1
if not self._data[front] then
return 1
end
return front
end
function CircularBuffer:front()
local front = self:getFrontIndex()
if self._data[front] then
return self._data[front].entry
end
return nil
end
function CircularBuffer:back()
if self._data[self._backIndex] then
return self._data[self._backIndex].entry
end
return nil
end
function CircularBuffer:iterator()
local front = self._data[self:getFrontIndex()]
local iterator = {
data = front,
next = function (self)
local retVal = self.data
if retVal then
self.data = self.data._next
end
return retVal and retVal.entry
end
}
return iterator
end
function CircularBuffer:getData()
return self._data
end
function CircularBuffer:at(ind)
assert(ind, "Cannot index CircularBuffer with nil")
local index = self:getFrontIndex()
index = (index + ind - 2) % self._maxSize + 1
if self._data[index] then
return self._data[index].entry
end
return nil
end
function CircularBuffer:reverseAt(ind)
local index = self._backIndex
index = (index - ind) % self._maxSize + 1
if self._data[index] then
return self._data[index].entry
end
return nil
end
-- returns the ejected element if newData overwrites
-- the previous front element
function CircularBuffer:push_back(newData)
local currBackIndex = self._backIndex
local newBackIndex = self._backIndex + 1
if newBackIndex > self._maxSize then
newBackIndex = 1
end
local overwrittenData = self._data[newBackIndex]
self._data[newBackIndex] = {
entry = newData
}
if currBackIndex > 0 then
self._data[currBackIndex]._next = self._data[newBackIndex]
if overwrittenData then
overwrittenData._next = nil
end
end
self._backIndex = newBackIndex
return overwrittenData and overwrittenData.entry
end
return CircularBuffer
@@ -0,0 +1,144 @@
return function()
local CircularBuffer = require(script.Parent.CircularBuffer)
it("should not allow initialize to size 0", function()
expect(function()
local buffer = CircularBuffer.new(0)
end).to.throw()
expect(function()
local buffer = CircularBuffer.new()
end).to.throw()
end)
it("should push_back() past its max size and loop over itself", function()
local buffer = CircularBuffer.new(1)
buffer:push_back("test1")
buffer:push_back("test2")
buffer:push_back("test3")
expect(buffer:getSize()).to.equal(1)
expect(buffer:front()).to.equal("test3")
buffer:setSize(5)
buffer:reset()
expect(buffer:getSize()).to.equal(0)
local expectedData = { 1, 2, 3, 4, 5 }
for _, v in ipairs(expectedData) do
buffer:push_back(v)
end
expect(buffer:front()).to.equal(1)
buffer:push_back(6)
expect(buffer:front()).to.equal(2)
end)
it("should sort and clip data during setSize() where applicable", function()
local buffer = CircularBuffer.new(100)
for i = 1, 100 do
buffer:push_back(i)
end
expect(buffer:getSize()).to.equal(100)
buffer:setSize(1)
expect(buffer:getSize()).to.equal(1)
buffer:setSize(100)
expect(buffer:getSize()).to.equal(1)
end)
it("should maintain the same front() value until it loops over itself", function()
local buffer = CircularBuffer.new(10)
expect(buffer:front()).to.equal(nil)
for i = 1, 10 do
buffer:push_back(i)
expect(buffer:front()).to.equal(1)
end
buffer:push_back(11)
expect(buffer:front()).to.equal(2)
buffer:push_back(12)
expect(buffer:front()).to.equal(3)
end)
it("should always return the last push_back() value when calling back()", function()
local buffer = CircularBuffer.new(10)
expect(buffer:back()).to.equal(nil)
for i = 1, 20 do
buffer:push_back(i)
expect(buffer:back()).to.equal(i)
end
end)
it("should iterate via iterator and terminate with a nil", function()
local buffer = CircularBuffer.new(100)
for i = 1, 100 do
buffer:push_back(i)
end
local it = buffer:iterator()
local val = it:next()
local count = 1
while val do
expect(val).to.equal(count)
val = it:next()
count = count + 1
end
expect(val).to.equal(nil)
end)
it("should maintain expected getData() after push_back()", function()
local buffer = CircularBuffer.new(5)
local expectedData = {1, 2, 3, 4, 5}
for _, v in ipairs(expectedData) do
buffer:push_back(v)
end
local data = buffer:getData()
for i,v in ipairs(expectedData) do
expect(data[i].entry).to.equal(v)
end
buffer:push_back(6)
local newExpectedData = {6, 2, 3, 4, 5}
data = buffer:getData()
for i,v in ipairs(newExpectedData) do
expect(data[i].entry).to.equal(v)
end
end)
it("should loop to access the correct values when using at() and reverseAt", function()
local buffer = CircularBuffer.new(10)
local expectedData = {2, 4, 6, 8, 0, 9, 7, 5, 3, 1}
for _,v in ipairs(expectedData) do
buffer:push_back(v)
end
for i, v in ipairs(expectedData) do
expect(buffer:at(i)).to.equal(v)
end
expect(buffer:at(0)).to.equal(1)
expect(buffer:at(100)).to.equal(1)
expect(buffer:at(-100)).to.equal(1)
expect(buffer:at(0)).to.equal(1)
expect(buffer:at(100)).to.equal(1)
expect(buffer:at(-100)).to.equal(1)
expect(buffer:reverseAt(1)).to.equal(1)
expect(buffer:reverseAt(5)).to.equal(9)
expect(buffer:reverseAt(0)).to.equal(2)
expect(buffer:reverseAt(100)).to.equal(2)
expect(buffer:reverseAt(-100)).to.equal(2)
end)
end
@@ -0,0 +1,350 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Components = script.Parent.Parent.Parent.Components
local DataConsumer = require(Components.DataConsumer)
local HeaderButton = require(Components.HeaderButton)
local CellLabel = require(Components.CellLabel)
local Constants = require(script.Parent.Parent.Parent.Constants)
local GeneralFormatting = Constants.GeneralFormatting
local LINE_WIDTH = GeneralFormatting.LineWidth
local LINE_COLOR = GeneralFormatting.LineColor
local ActionBindingsFormatting = Constants.ActionBindingsFormatting
local HEADER_NAMES = ActionBindingsFormatting.ChartHeaderNames
local CELL_WIDTHS = ActionBindingsFormatting.ChartCellWidths
local HEADER_HEIGHT = ActionBindingsFormatting.HeaderFrameHeight
local ENTRY_HEIGHT = ActionBindingsFormatting.EntryFrameHeight
local CELL_PADDING = ActionBindingsFormatting.CellPadding
local MIN_FRAME_WIDTH = ActionBindingsFormatting.MinFrameWidth
local IS_CORE_STR = "Core"
local IS_DEVELOPER_STR = "Developer"
local NON_FOUND_ENTRIES_STR = "No ActionBindings Found"
-- create table of offsets and sizes for each cell
local totalCellWidth = 0
for _, cellWidth in ipairs(CELL_WIDTHS) do
totalCellWidth = totalCellWidth + cellWidth
end
local currOffset = -totalCellWidth
local cellOffset = {}
local headerCellSize = {}
local entryCellSize = {}
currOffset = currOffset / 2
table.insert(cellOffset, UDim2.new(0, CELL_PADDING, 0, 0))
table.insert(headerCellSize, UDim2.new(.5, currOffset - CELL_PADDING, 0, HEADER_HEIGHT))
table.insert(entryCellSize, UDim2.new(.5, currOffset - CELL_PADDING, 0, ENTRY_HEIGHT))
for _, cellWidth in ipairs(CELL_WIDTHS) do
table.insert(cellOffset,UDim2.new(.5, currOffset + CELL_PADDING, 0, 0))
table.insert(headerCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, HEADER_HEIGHT))
table.insert(entryCellSize, UDim2.new(0, cellWidth - CELL_PADDING, 0, ENTRY_HEIGHT))
currOffset = currOffset + cellWidth
end
table.insert(cellOffset,UDim2.new(.5, currOffset + CELL_PADDING, 0, 0))
table.insert(headerCellSize, UDim2.new(.5, (-totalCellWidth / 2) - CELL_PADDING, 0, HEADER_HEIGHT))
table.insert(entryCellSize, UDim2.new(.5, (-totalCellWidth / 2) - CELL_PADDING, 0, ENTRY_HEIGHT))
local verticalOffsets = {}
for i, offset in ipairs(cellOffset) do
verticalOffsets[i] = UDim2.new(
offset.X.Scale,
offset.X.Offset - CELL_PADDING,
offset.Y.Scale,
offset.Y.Offset)
end
local ActionBindingsChart = Roact.Component:extend("ActionBindingsChart")
local function constructHeader(onSortChanged, width)
local header = {}
for ind, name in ipairs(HEADER_NAMES) do
header[name] = Roact.createElement(HeaderButton, {
text = name,
size = headerCellSize[ind],
pos = cellOffset[ind],
sortfunction = onSortChanged,
})
end
header["upperHorizontalLine"] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 0, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
header["lowerHorizontalLine"] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, -LINE_WIDTH),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
for ind = 2, #verticalOffsets do
local key = string.format("VerticalLine_%d",ind)
header[key] = Roact.createElement("Frame", {
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
Position = verticalOffsets[ind],
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
end
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
BackgroundTransparency = 1,
ClipsDescendants = true,
}, header)
end
local function constructEntry(entry, width, layoutOrder)
local name = entry.name
local actionInfo = entry.actionInfo
-- the last element is special cased because the data in the
-- string is passed in as value in the table
-- use tostring to convert the enum into an actual string also because it's used twice
local enumStr = tostring(actionInfo["inputTypes"][1])
local isCoreString = IS_CORE_STR
if actionInfo["isCore"] then
isCoreString = IS_DEVELOPER_STR
end
local row = {}
for i = 2,#verticalOffsets do
local key = string.format("line_%d",i)
row[key] = Roact.createElement("Frame", {
Size = UDim2.new(0,LINE_WIDTH,1,0),
Position = verticalOffsets[i],
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
end
row[name] = Roact.createElement(CellLabel, {
text = enumStr,
size = entryCellSize[1],
pos = cellOffset[1],
})
row.priorityLevel = Roact.createElement(CellLabel, {
text = actionInfo["priorityLevel"],
size = entryCellSize[2],
pos = cellOffset[2],
})
row.isCore = Roact.createElement(CellLabel, {
text = isCoreString,
size = entryCellSize[3],
pos = cellOffset[3],
})
row.actionName = Roact.createElement(CellLabel, {
text = name,
size = entryCellSize[4],
pos = cellOffset[4],
})
row.inputTypes = Roact.createElement(CellLabel, {
text = enumStr,
size = entryCellSize[5],
pos = cellOffset[5],
})
row.lowerHorizontalLine = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
return Roact.createElement("Frame", {
Size = UDim2.new(0, width, 0, ENTRY_HEIGHT),
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
},row)
end
function ActionBindingsChart:init(props)
local initBindings = props.ActionBindingsData:getCurrentData()
self.onSortChanged = function(sortType)
local currSortType = props.ActionBindingsData:getSortType()
if sortType == currSortType then
self:setState({
reverseSort = not self.state.reverseSort
})
else
props.ActionBindingsData:setSortType(sortType)
self:setState({
reverseSort = false,
})
end
end
self.onCanvasPosChanged = function()
local canvasPos = self.scrollingRef.current.CanvasPosition
if self.state.canvasPos ~= canvasPos then
self:setState({
canvasPos = canvasPos,
})
end
end
self.scrollingRef = Roact.createRef()
self.state = {
actionBindingEntries = initBindings,
reverseSort = false,
}
end
function ActionBindingsChart:willUpdate()
if self.canvasPosConnector then
self.canvasPosConnector:Disconnect()
end
end
function ActionBindingsChart:didUpdate()
if self.scrollingRef.current then
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
local absSize = self.scrollingRef.current.AbsoluteSize
local currAbsSize = self.state.absScrollSize
if absSize.X ~= currAbsSize.X or
absSize.Y ~= currAbsSize.Y then
self:setState({
absScrollSize = absSize,
})
end
end
end
function ActionBindingsChart:didMount()
self.bindingsUpdated = self.props.ActionBindingsData:Signal():Connect(function(bindingsData)
self:setState({
actionBindingEntries = bindingsData
})
end)
if self.scrollingRef.current then
local signal = self.scrollingRef.current:GetPropertyChangedSignal("CanvasPosition")
self.canvasPosConnector = signal:Connect(self.onCanvasPosChanged)
self:setState({
absScrollSize = self.scrollingRef.current.AbsoluteSize,
canvasPos = self.scrollingRef.current.CanvasPosition,
})
end
end
function ActionBindingsChart:willUnmount()
self.bindingsUpdated:Disconnect()
self.bindingsUpdated = nil
self.canvasPosConnector:Disconnect()
self.canvasPosConnector = nil
end
function ActionBindingsChart:render()
local entries = {}
local searchTerm = self.props.searchTerm
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local entryList = self.state.actionBindingEntries
local reverseSort = self.state.reverseSort
local canvasPos = self.state.canvasPos
local absScrollSize = self.state.absScrollSize
local frameWidth = absScrollSize and math.max(absScrollSize.X, MIN_FRAME_WIDTH) or MIN_FRAME_WIDTH
entries["UIListLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
SortOrder = Enum.SortOrder.LayoutOrder,
})
local totalEntries = #entryList
local canvasHeight = 0
if absScrollSize and canvasPos then
local paddingHeight = -1
local usedFrameSpace = 0
local count = 0
for ind, entry in ipairs(entryList) do
local foundTerm = false
if searchTerm then
local enumStr = tostring(entry.actionInfo["inputTypes"][1])
foundTerm = string.find(enumStr:lower(), searchTerm:lower()) ~= nil
foundTerm = foundTerm or string.find(entry.name:lower(), searchTerm:lower()) ~= nil
end
if not searchTerm or foundTerm then
if canvasHeight + ENTRY_HEIGHT >= canvasPos.Y then
if usedFrameSpace < absScrollSize.Y then
local entryLayoutOrder = reverseSort and (totalEntries - ind) or ind
entries[ind] = constructEntry(entry, frameWidth, entryLayoutOrder + 1)
end
if paddingHeight < 0 then
paddingHeight = canvasHeight
else
usedFrameSpace = usedFrameSpace + ENTRY_HEIGHT
end
count = count + 1
end
canvasHeight = canvasHeight + ENTRY_HEIGHT
end
end
if count == 0 then
entries["NoneFound"] = Roact.createElement("TextLabel", {
Size = UDim2.new(1, 0, 1, 0),
Text = NON_FOUND_ENTRIES_STR,
TextColor3 = LINE_COLOR,
BackgroundTransparency = 1,
LayoutOrder = 1,
})
else
entries["WindowingPadding"] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, paddingHeight),
BackgroundTransparency = 1,
LayoutOrder = 1,
})
end
end
return Roact.createElement("Frame", {
Size = size,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, {
Header = constructHeader(self.onSortChanged, frameWidth),
MainChart = Roact.createElement("ScrollingFrame", {
Position = UDim2.new(0, 0, 0, HEADER_HEIGHT),
Size = UDim2.new(1, 0, 1, - HEADER_HEIGHT),
CanvasSize = UDim2.new(0, frameWidth, 0, canvasHeight),
ScrollBarThickness = 6,
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
[Roact.Ref] = self.scrollingRef
}, entries),
})
end
return DataConsumer(ActionBindingsChart, "ActionBindingsData")
@@ -0,0 +1,15 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DataProvider = require(script.Parent.Parent.DataProvider)
local ActionBindingsChart = require(script.Parent.ActionBindingsChart)
it("should create and destroy without errors", function()
local element = Roact.createElement(DataProvider, nil, {
ActionBindingsChart = Roact.createElement(ActionBindingsChart)
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,151 @@
local ContextActionService = game:GetService("ContextActionService")
local Signal = require(script.Parent.Parent.Parent.Signal)
local Constants = require(script.Parent.Parent.Parent.Constants)
local HEADER_NAMES = Constants.ActionBindingsFormatting.ChartHeaderNames
local SORT_COMPARATOR = {
[HEADER_NAMES[1]] = function(a,b) -- "Name"
return a.counter < b.counter
end,
[HEADER_NAMES[2]] = function(a,b) -- "Priority"
if a.actionInfo.priorityLevel == b.actionInfo.priorityLevel then
return a.counter < b.counter
end
return a.actionInfo.priorityLevel < b.actionInfo.priorityLevel
end,
[HEADER_NAMES[3]] = function(a,b) -- "Security"
if a.actionInfo.isCore == b.actionInfo.isCore then
return a.counter < b.counter
else
return a.actionInfo.isCore
end
end,
[HEADER_NAMES[4]] = function(a,b) -- "Action Name"
return a.name:lower() < b.name:lower()
end,
[HEADER_NAMES[5]] = function(a,b) -- "Input Types"
return tostring(a.actionInfo.inputTypes[1]) < tostring(b.actionInfo.inputTypes[1])
end,
}
local ActionBindingsData = {}
ActionBindingsData.__index = ActionBindingsData
function ActionBindingsData.new()
local self = {}
setmetatable(self, ActionBindingsData)
self._bindingsUpdated = Signal.new()
self._bindingsData = {}
self._bindingCounter = 0
self._sortedBindingData = {}
self._sortType = HEADER_NAMES[1] -- Name
return self
end
function ActionBindingsData:setSortType(sortType)
if SORT_COMPARATOR[sortType] then
self._sortType = sortType
table.sort(self._sortedBindingData, SORT_COMPARATOR[self._sortType])
self._bindingsUpdated:Fire(self._sortedBindingData)
else
error(string.format("attempted to pass invalid sortType: %s", tostring(sortType)), 2)
end
end
function ActionBindingsData:getSortType()
return self._sortType
end
function ActionBindingsData:Signal()
return self._bindingsUpdated
end
function ActionBindingsData:getCurrentData()
return self._sortedBindingData
end
-- this funciton will require some extra work to handle the
-- case a entry insert occurs during the end of the list
function ActionBindingsData:updateBindingDataEntry(name, info)
if info == nil then
--remove element and clean up sorted
self._bindingsData[name] = nil
for i, v in pairs(self._sortedBindingData) do
if v.name == name then
table.remove(self._sortedBindingData, i)
return
end
end
elseif not self._bindingsData[name] then
self._bindingCounter = self._bindingCounter + 1
self._bindingsData[name] = info
local newEntry = {
name = name,
actionInfo = self._bindingsData[name],
counter = self._bindingCounter,
}
table.insert(self._sortedBindingData, newEntry)
else
self._bindingsData[name] = info
end
end
function ActionBindingsData:start()
local boundActions = ContextActionService:GetAllBoundActionInfo()
for actionName, actionInfo in pairs(boundActions) do
actionInfo.isCore = false
self:updateBindingDataEntry(actionName, actionInfo)
end
local boundCoreActions = ContextActionService:GetAllBoundCoreActionInfo()
for actionName, actionInfo in pairs(boundCoreActions) do
actionInfo.isCore = true
self:updateBindingDataEntry(actionName, actionInfo)
end
if not self._actionChangedConnection then
self._actionChangedConnection = ContextActionService.BoundActionChanged:connect(
function(actionName, changeName, changeTable)
self:updateBindingDataEntry(actionName, nil)
self:updateBindingDataEntry(changeName, changeTable)
self._bindingsUpdated:Fire(self._sortedBindingData)
end)
end
if not self._actionAddedConnection then
self._actionAddedConnection = ContextActionService.BoundActionAdded:connect(
function(actionName, createTouchButton, actionInfo, isCore)
actionInfo.isCore = isCore
self:updateBindingDataEntry(actionName, actionInfo)
self._bindingsUpdated:Fire(self._sortedBindingData)
end)
end
if not self._actionRemovedConnection then
self._actionRemovedConnection = ContextActionService.BoundActionRemoved:connect(
function(actionName, actionInfo, isCore)
self:updateBindingDataEntry(actionName, nil)
self._bindingsUpdated:Fire(self._sortedBindingData)
end)
end
end
function ActionBindingsData:stop()
if self.actionChangedConnector then
self.actionChangedConnector:Disconnect()
self.actionChangedConnector = nil
end
if self.actionAddedConnector then
self.actionAddedConnector:Disconnect()
self.actionAddedConnector = nil
end
if self.actionRemovedConnector then
self.actionRemovedConnector:Disconnect()
self.actionRemovedConnector = nil
end
end
return ActionBindingsData
@@ -0,0 +1,105 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Components = script.Parent.Parent.Parent.Components
local ActionBindingsChart = require(Components.ActionBindings.ActionBindingsChart)
local UtilAndTab = require(Components.UtilAndTab)
local Actions = script.Parent.Parent.Parent.Actions
local ActionBindingsUpdateSearchFilter = require(Actions.ActionBindingsUpdateSearchFilter)
local Constants = require(script.Parent.Parent.Parent.Constants)
local PADDING = Constants.GeneralFormatting.MainRowPadding
local MainViewActionBindings = Roact.Component:extend("MainViewActionBindings")
function MainViewActionBindings:init()
self.onUtilTabHeightChanged = function(utilTabHeight)
self:setState({
utilTabHeight = utilTabHeight
})
end
self.onSearchTermChanged = function(newSearchTerm)
self.props.dispatchActionBindingsUpdateSearchFilter(newSearchTerm, {})
end
self.utilRef = Roact.createRef()
self.state = {
utilTabHeight = 0
}
end
function MainViewActionBindings:didMount()
local utilSize = self.utilRef.current.Size
self:setState({
utilTabHeight = utilSize.Y.Offset
})
end
function MainViewActionBindings:didUpdate()
local utilSize = self.utilRef.current.Size
if utilSize.Y.Offset ~= self.state.utilTabHeight then
self:setState({
utilTabHeight = utilSize.Y.Offset
})
end
end
function MainViewActionBindings:render()
local size = self.props.size
local formFactor = self.props.formFactor
local tabList = self.props.tabList
local searchTerm = self.props.bindingsSearchTerm
local utilTabHeight = self.state.utilTabHeight
return Roact.createElement("Frame", {
Size = size,
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
LayoutOrder = 3,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, PADDING),
SortOrder = Enum.SortOrder.LayoutOrder,
}),
UtilAndTab = Roact.createElement(UtilAndTab, {
windowWidth = size.X.Offset,
formFactor = formFactor,
tabList = tabList,
searchTerm = searchTerm,
layoutOrder = 1,
refForParent = self.utilRef,
onHeightChanged = self.onUtilTabHeightChanged,
onSearchTermChanged = self.onSearchTermChanged,
}),
ActionBindings = utilTabHeight > 0 and Roact.createElement(ActionBindingsChart, {
size = UDim2.new(1, 0, 1, -utilTabHeight),
searchTerm = searchTerm,
layoutOrder = 2,
}),
})
end
local function mapStateToProps(state, props)
return {
bindingsSearchTerm = state.ActionBindingsData.bindingsSearchTerm,
}
end
local function mapDispatchToProps(dispatch)
return {
dispatchActionBindingsUpdateSearchFilter = function(searchTerm, filters)
dispatch(ActionBindingsUpdateSearchFilter(searchTerm, filters))
end
}
end
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewActionBindings)
@@ -0,0 +1,36 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Store = require(CorePackages.Rodux).Store
local DataProvider = require(script.Parent.Parent.DataProvider)
local MainViewActionBindings = require(script.Parent.MainViewActionBindings)
it("should create and destroy without errors", function()
local store = Store.new(function()
return {
MainView = {
currTabIndex = 0
},
ActionBindingsData = {
bindingsSearchTerm = ""
}
}
end)
local element = Roact.createElement(RoactRodux.StoreProvider, {
store = store,
}, {
DataProvider = Roact.createElement(DataProvider, {}, {
MainViewActionBindings = Roact.createElement(MainViewActionBindings, {
size = UDim2.new(),
tabList = {},
})
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,56 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Immutable = require(script.Parent.Parent.Immutable)
local Constants = require(script.Parent.Parent.Constants)
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
local LINE_COLOR = Constants.GeneralFormatting.LineColor
local ARROW_WIDTH = Constants.GeneralFormatting.ArrowWidth
local CLOSE_ARROW = Constants.Image.RightArrow
local OPEN_ARROW = Constants.Image.DownArrow
local BannerButton = Roact.Component:extend("BannerButton")
function BannerButton:render()
local children = self.props[Roact.Children] or {}
local size = self.props.size
local pos = self.props.pos
local isExpanded = self.props.isExpanded
local layoutOrder = self.props.layoutOrder
local onButtonPress = self.props.onButtonPress
local bannerElements = {
BannerButtonArrow = Roact.createElement("ImageLabel", {
Image = isExpanded and OPEN_ARROW or CLOSE_ARROW,
BackgroundTransparency = 1,
Size = UDim2.new(0, ARROW_WIDTH, 0, ARROW_WIDTH),
Position = UDim2.new(0, 0, .5, -ARROW_WIDTH / 2),
}),
HorizontalLineTop = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
HorizontalLineBottom = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, -LINE_WIDTH),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
}
return Roact.createElement("ImageButton", {
Size = size,
Position = pos,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
[Roact.Event.Activated] = onButtonPress,
}, Immutable.JoinDictionaries(bannerElements, children))
end
return BannerButton
@@ -0,0 +1,32 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local TEXT_SIZE = Constants.DefaultFontSize.MainWindow
local TEXT_COLOR = Constants.Color.Text
local MAIN_FONT = Constants.Font.MainWindow
local MAIN_FONT_BOLD = Constants.Font.MainWindowBold
local function CellLabel(props)
local text = props.text
local size = props.size
local pos = props.pos
local bold = props.bold
local layoutOrder = props.layoutOrder
return Roact.createElement("TextLabel", {
Text = text,
TextSize = TEXT_SIZE,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
TextWrapped = true,
Font = bold and MAIN_FONT_BOLD or MAIN_FONT,
Size = size,
Position = pos,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
})
end
return CellLabel
@@ -0,0 +1,70 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local PADDING = Constants.UtilityBarFormatting.CheckBoxInnerPadding
local CheckBox = Roact.Component:extend("CheckBox")
function CheckBox:render()
local checkBoxHeight = self.props.checkBoxHeight
local frameHeight = self.props.frameHeight
local layoutOrder = self.props.layoutOrder
local name = self.props.name
local font = self.props.font
local fontSize = self.props.fontSize
local isSelected = self.props.isSelected
local selectedColor = self.props.selectedColor
local unselectedColor = self.props.unselectedColor
local onCheckBoxClicked = self.props.onCheckBoxClicked
-- this can be replaced with default values once that releases
local image = ""
local borderSize = 1
local backgroundColor = unselectedColor
if isSelected then
image = Constants.Image.Check
borderSize = 0
backgroundColor = selectedColor
end
local textVector = TextService:GetTextSize(name, fontSize, font, Vector2.new(0, frameHeight))
local textWidth = textVector.X
return Roact.createElement("ImageButton", {
Size = UDim2.new(0, checkBoxHeight + textWidth + (PADDING * 2), 0, frameHeight),
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
[Roact.Event.Activated] = function(rbx)
onCheckBoxClicked(name, not isSelected)
end,
}, {
Icon = Roact.createElement("ImageLabel", {
Image = image,
Size = UDim2.new(0, checkBoxHeight, 0, checkBoxHeight),
Position = UDim2.new(0, 0, .5, -checkBoxHeight / 2),
BackgroundColor3 = backgroundColor,
BackgroundTransparency = 0,
BorderColor3 = Constants.Color.Text,
BorderSizePixel = borderSize,
}),
Text = Roact.createElement("TextLabel", {
Text = name,
TextColor3 = Constants.Color.Text,
TextXAlignment = Enum.TextXAlignment.Left,
Font = font,
TextSize = fontSize,
Size = UDim2.new(1, -frameHeight, 1, 0),
Position = UDim2.new(0, checkBoxHeight + PADDING, 0, 0),
BackgroundTransparency = 1,
})
})
end
return CheckBox
@@ -0,0 +1,18 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local CheckBox = require(script.Parent.CheckBox)
it("should create and destroy without errors", function()
local element = Roact.createElement(CheckBox, {
name = "",
fontSize = 0,
font = 0,
frameHeight = 0,
checkBoxHeight = 0,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,161 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local Components = script.Parent.Parent.Components
local CheckBox = require(Components.CheckBox)
local CheckBoxDropDown = require(Components.CheckBoxDropDown)
local Constants = require(script.Parent.Parent.Constants)
local CHECK_BOX_HEIGHT = Constants.UtilityBarFormatting.CheckBoxHeight
local CHECK_BOX_PADDING = Constants.UtilityBarFormatting.CheckBoxInnerPadding * 2
local FILTER_ICON_UNFILLED = Constants.Image.FilterUnfilled
local FILTER_ICON_FILLED = Constants.Image.FilterFilled
local DROP_DOWN_Y_ADJUST = 3
local CheckBoxContainer = Roact.PureComponent:extend("CheckBoxContainer")
function CheckBoxContainer:init()
self.onCheckBoxClicked = function(field, newState)
local onCheckBoxChanged = self.props.onCheckBoxChanged
onCheckBoxChanged(field, newState)
end
-- this is part of the dropdown logic
self.onCheckBoxExpanded = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
(input.UserInputType == Enum.UserInputType.Touch and
input.UserInputState == Enum.UserInputState.End) then
self:setState({
expanded = true,
})
end
end
-- this is part of the dropdown logic
self.onCloseCheckBox = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
(input.UserInputType == Enum.UserInputType.Touch and
input.UserInputState == Enum.UserInputState.End) then
self:setState({
expanded = false
})
end
end
if not self.props.orderedCheckBoxState then
warn("CheckBoxContainer must be passed a list of Box Names or else it only creates an empty frame")
end
local textWidths = {}
local totalLength = 0
local count = 0
for ind, box in ipairs(self.props.orderedCheckBoxState) do
local textVector = TextService:GetTextSize(
box.name,
Constants.DefaultFontSize.UtilBar,
Constants.Font.UtilBar,
Vector2.new(0, 0)
)
textWidths[ind] = textVector.X
totalLength = totalLength + textVector.X + CHECK_BOX_HEIGHT + CHECK_BOX_PADDING
count = count + 1
end
self.ref = Roact.createRef()
self.state = {
expanded = false,
textWidths = textWidths,
numCheckBoxes = count,
minFullLength = totalLength,
}
end
function CheckBoxContainer:render()
local elements = {}
local frameWidth = self.props.frameWidth
local frameHeight = self.props.frameHeight
local pos = self.props.pos
local layoutOrder = self.props.layoutOrder
local orderedCheckBoxState = self.props.orderedCheckBoxState
local minFullLength = self.state.minFullLength
local expanded = self.state.expanded
local numCheckBoxes = self.state.numCheckBoxes
local anySelected = false
for layoutOrder, box in ipairs(orderedCheckBoxState) do
elements[box.name] = Roact.createElement(CheckBox, {
name = box.name,
font = Constants.Font.UtilBar,
fontSize = Constants.DefaultFontSize.UtilBar,
checkBoxHeight = CHECK_BOX_HEIGHT,
frameHeight = frameHeight,
layoutOrder = layoutOrder,
isSelected = box.state,
selectedColor = Constants.Color.SelectedBlue,
unselectedColor = Constants.Color.UnselectedGray,
onCheckBoxClicked = self.onCheckBoxClicked,
})
anySelected = anySelected or box.state
end
if frameWidth < minFullLength then
elements["CheckBoxLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirection = Enum.FillDirection.Vertical,
})
local showDropDown = self.ref.current and expanded
local dropDownPos
if showDropDown then
local absPos = self.ref.current.AbsolutePosition
-- adding slight y offset to nudge dropdown enough to see button border
dropDownPos = UDim2.new(0, absPos.X, 0, absPos.Y + frameHeight + DROP_DOWN_Y_ADJUST)
end
return Roact.createElement("ImageButton", {
Size = UDim2.new(0, frameHeight, 0, frameHeight),
LayoutOrder = layoutOrder,
Image = showDropDown and FILTER_ICON_FILLED or FILTER_ICON_UNFILLED,
BackgroundTransparency = 1,
BorderColor3 = Constants.Color.Text,
[Roact.Event.InputEnded] = self.onCheckBoxExpanded,
[Roact.Ref] = self.ref,
}, {
DropDown = showDropDown and Roact.createElement(CheckBoxDropDown, {
absolutePosition = dropDownPos,
frameWidth = frameWidth,
elementHeight = frameHeight,
numElements = numCheckBoxes,
onCloseCheckBox = self.onCloseCheckBox
}, elements)
})
else
elements["CheckBoxLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirection = Enum.FillDirection.Horizontal,
})
return Roact.createElement("Frame", {
Size = UDim2.new(0,frameWidth,0,frameHeight),
Position = pos,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, elements)
end
end
return CheckBoxContainer
@@ -0,0 +1,15 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local CheckBoxContainer = require(script.Parent.CheckBoxContainer)
it("should create and destroy without errors", function()
local element = Roact.createElement(CheckBoxContainer, {
orderedCheckBoxState = {},
frameWidth = 0,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,52 @@
local CorePackages = game:GetService("CorePackages")
local RobloxGui = game:GetService("CoreGui").RobloxGui
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local INNER_FRAME_PADDING = 12
local CheckBoxDropDown = Roact.Component:extend("CheckBoxDropDown")
function CheckBoxDropDown:render()
local children = self.props[Roact.Children] or {}
local absolutePosition = self.props.absolutePosition
local frameWidth = self.props.frameWidth
local elementHeight = self.props.elementHeight
local numElements = self.props.numElements
local onCloseCheckBox = self.props.onCloseCheckBox
local frameHeight = elementHeight * numElements
local outerFrameSize = UDim2.new( 0, frameWidth, 0, (2 * INNER_FRAME_PADDING) + frameHeight)
return Roact.createElement(Roact.Portal, {
target = RobloxGui,
}, {
FullScreen = Roact.createElement("ScreenGui", {
}, {
InputCatcher = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
BackgroundTransparency = 1,
[Roact.Event.InputEnded] = onCloseCheckBox,
}, {
OuterFrame = Roact.createElement("ImageButton", {
Size = outerFrameSize,
AutoButtonColor = false,
Position = absolutePosition,
BackgroundColor3 = Constants.Color.TextBoxGray,
BackgroundTransparency = 0,
}, {
innerFrame = Roact.createElement("Frame", {
Position = UDim2.new(0, INNER_FRAME_PADDING, 0 , INNER_FRAME_PADDING),
Size = UDim2.new(0,frameWidth, 0, frameHeight),
BackgroundTransparency = 1,
}, children)
})
})
})
})
end
return CheckBoxDropDown
@@ -0,0 +1,15 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local CheckBoxDropDown = require(script.Parent.CheckBoxDropDown)
it("should create and destroy without errors", function()
local element = Roact.createElement(CheckBoxDropDown, {
elementHeight = 0,
numElements = 0,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,101 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local FRAME_HEIGHT = Constants.UtilityBarFormatting.FrameHeight
local SMALL_FRAME_HEIGHT = Constants.UtilityBarFormatting.SmallFrameHeight
local CS_BUTTON_WIDTH = Constants.UtilityBarFormatting.ClientServerButtonWidth
local SMALL_CS_BUTTON_WIDTH = Constants.UtilityBarFormatting.ClientServerDropDownWidth
local FONT = Constants.Font.UtilBar
local FONT_SIZE = Constants.DefaultFontSize.UtilBar
local FONT_COLOR = Constants.Color.Text
local FullScreenDropDownButton = require(script.Parent.FullScreenDropDownButton)
local DropDown = require(script.Parent.DropDown)
local BUTTON_SIZE = UDim2.new(0, CS_BUTTON_WIDTH, 0, FRAME_HEIGHT)
local CLIENT_SERVER_NAMES = {"Client", "Server"}
local ClientServerButton = Roact.Component:extend("ClientServerButton")
function ClientServerButton:init()
self.dropDownCallback = function(index)
if index == 1 then
self.props.onClientButton()
elseif index == 2 then
self.props.onServerButton()
end
end
end
function ClientServerButton:render()
local formFactor = self.props.formFactor
local useDropDown = self.props.useDropDown
local isClientView = self.props.isClientView
local layoutOrder = self.props.layoutOrder
local onServerButton = self.props.onServerButton
local onClientButton = self.props.onClientButton
local serverButtonColor = Constants.Color.SelectedBlue
local clientButtonColor = Constants.Color.UnselectedGray
if isClientView then
clientButtonColor = Constants.Color.SelectedBlue
serverButtonColor = Constants.Color.UnselectedGray
end
if formFactor == Constants.FormFactor.Small then
return Roact.createElement(FullScreenDropDownButton, {
buttonSize = UDim2.new(0, SMALL_CS_BUTTON_WIDTH, 0, SMALL_FRAME_HEIGHT),
dropDownList = CLIENT_SERVER_NAMES,
selectedIndex = isClientView and 1 or 2,
onSelection = self.dropDownCallback,
layoutOrder = layoutOrder,
})
elseif useDropDown then
return Roact.createElement(DropDown, {
buttonSize = UDim2.new(0, SMALL_CS_BUTTON_WIDTH, 0, SMALL_FRAME_HEIGHT),
dropDownList = CLIENT_SERVER_NAMES,
selectedIndex = isClientView and 1 or 2,
onSelection = self.dropDownCallback,
layoutOrder = layoutOrder,
})
else
return Roact.createElement("Frame", {
Size = UDim2.new(0, 2 * CS_BUTTON_WIDTH, 0, FRAME_HEIGHT),
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, {
ClientButton = Roact.createElement('TextButton', {
Text = CLIENT_SERVER_NAMES[1],
TextSize = FONT_SIZE,
TextColor3 = FONT_COLOR,
Font = FONT,
Size = BUTTON_SIZE,
AutoButtonColor = false,
BackgroundColor3 = clientButtonColor,
BackgroundTransparency = 0,
LayoutOrder = 1,
[Roact.Event.Activated] = onClientButton,
}),
ServerButton = Roact.createElement('TextButton', {
Text = CLIENT_SERVER_NAMES[2],
TextSize = FONT_SIZE,
TextColor3 = FONT_COLOR,
Font = FONT,
Size = BUTTON_SIZE,
AutoButtonColor = false,
Position = UDim2.new(0, CS_BUTTON_WIDTH, 0, 0),
BackgroundColor3 = serverButtonColor,
BackgroundTransparency = 0,
LayoutOrder = 2,
[Roact.Event.Activated] = onServerButton,
})
})
end
end
return ClientServerButton
@@ -0,0 +1,13 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local ClientServerButton = require(script.Parent.ClientServerButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(ClientServerButton
)
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,41 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Immutable = require(script.Parent.Parent.Immutable)
return function(component, ...)
if not component then
error("Expected component to be passed to connection, got nil.")
end
local targetList = {}
for i = 1, select("#", ...) do
targetList[i] = select(i, ...)
end
local name = string.format("Consumer(%s)_DependsOn_%s",tostring(component), targetList[1] )
local DataConsumer = Roact.Component:extend(name)
function DataConsumer:init()
local contextTable = {}
for _,dataName in pairs(targetList) do
local contextualData = self._context.DevConsoleData[dataName]
if not contextualData then
local errorStr = string.format("%s %s",tostring(dataName),
"could not be found. Make sure DataProvider is above this consumer"
)
error(errorStr)
return
end
contextTable[dataName] = contextualData
end
self.state = contextTable
end
function DataConsumer:render()
local props = Immutable.JoinDictionaries(self.props, self.state)
return Roact.createElement(component, props)
end
return DataConsumer
end
@@ -0,0 +1,49 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Components = script.Parent.Parent.Components
local LogData = require(Components.Log.LogData)
local ClientMemoryData = require(Components.Memory.ClientMemoryData)
local ServerMemoryData = require(Components.Memory.ServerMemoryData)
local NetworkData = require(Components.Network.NetworkData)
local ServerScriptsData = require(Components.Scripts.ServerScriptsData)
local DataStoresData = require(Components.DataStores.DataStoresData)
local ServerStatsData = require(Components.ServerStats.ServerStatsData)
local ActionBindingsData = require(Components.ActionBindings.ActionBindingsData)
local ServerJobsData = require(Components.ServerJobs.ServerJobsData)
local DataProvider = Roact.Component:extend("DataProvider")
function DataProvider:init()
self._context.DevConsoleData = {
ClientLogData = LogData.new(true),
ServerLogData = LogData.new(false),
ClientMemoryData = ClientMemoryData.new(),
ServerMemoryData = ServerMemoryData.new(),
ClientNetworkData = NetworkData.new(true),
ServerNetworkData = NetworkData.new(false),
ServerScriptsData = ServerScriptsData.new(),
DataStoresData = DataStoresData.new(),
ServerStatsData = ServerStatsData.new(),
ActionBindingsData = ActionBindingsData.new(),
ServerJobsData = ServerJobsData.new(),
}
end
function DataProvider:didMount()
if self.props.isDeveloperView then
for _, dataProvider in pairs(self._context.DevConsoleData) do
dataProvider:start()
end
else
self._context.DevConsoleData.ClientLogData:start()
self._context.DevConsoleData.ClientMemoryData:start()
end
end
function DataProvider:render()
return Roact.oneChild(self.props[Roact.Children])
end
return DataProvider
@@ -0,0 +1,239 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Components = script.Parent.Parent.Parent.Components
local DataConsumer = require(Components.DataConsumer)
local CellLabel = require(Components.CellLabel)
local BannerButton = require(Components.BannerButton)
local LineGraph = require(Components.LineGraph)
local Constants = require(script.Parent.Parent.Parent.Constants)
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
local LINE_COLOR = Constants.GeneralFormatting.LineColor
local HEADER_NAMES = Constants.DataStoresFormatting.ChartHeaderNames
local VALUE_CELL_WIDTH = Constants.DataStoresFormatting.ValueCellWidth
local CELL_PADDING = Constants.DataStoresFormatting.CellPadding
local ARROW_PADDING = Constants.DataStoresFormatting.ExpandArrowPadding
local HEADER_HEIGHT = Constants.DataStoresFormatting.HeaderFrameHeight
local ENTRY_HEIGHT = Constants.DataStoresFormatting.EntryFrameHeight
local GRAPH_HEIGHT = Constants.GeneralFormatting.LineGraphHeight
local NO_DATA_MSG = "Initialize DataStoresService to view DataStore Budget."
local NO_RESULT_SEARCH_STR = Constants.GeneralFormatting.NoResultSearchStr
local convertTimeStamp = require(script.Parent.Parent.Parent.Util.convertTimeStamp)
local DataStoresChart = Roact.Component:extend("DataStoresChart")
local function getX(entry)
return entry.time
end
local function getY(entry)
return entry.value
end
local function stringFormatY(value)
return math.ceil(value)
end
function DataStoresChart:init(props)
local currStoresData, currStoresDataCount = props.DataStoresData:getCurrentData()
self.getOnButtonPress = function (name)
return function(rbx, input)
self:setState({
expandedEntry = self.state.expandedEntry ~= name and name
})
end
end
self.state = {
dataStoresData = currStoresData,
dataStoresDataCount = currStoresDataCount,
expandedEntry = nil
}
end
function DataStoresChart:didMount()
self.statsConnector = self.props.DataStoresData:Signal():Connect(function(data, count)
self:setState({
dataStoresData = data,
dataStoresDataCount = count,
})
end)
end
function DataStoresChart:willUnmount()
self.statsConnector:Disconnect()
self.statsConnector = nil
end
function DataStoresChart:render()
local elements = {}
local searchTerm = self.props.searchTerm
local size = self.props.size
local layoutOrder = self.props.layoutOrder
local expandedEntry = self.state.expandedEntry
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
SortOrder = Enum.SortOrder.LayoutOrder,
})
local componentHeight = HEADER_HEIGHT
local datastoreBudget = self.state.dataStoresData
local totalCount = 0
local currLayoutOrder = 1
if datastoreBudget then
for name, data in pairs(datastoreBudget) do
totalCount = totalCount + 1
if not searchTerm or string.find(name:lower(), searchTerm:lower()) ~= nil then
currLayoutOrder = currLayoutOrder + 1
local showGraph = expandedEntry == name
local frameHeight = showGraph and ENTRY_HEIGHT + GRAPH_HEIGHT or ENTRY_HEIGHT
elements[name] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, frameHeight),
BackgroundTransparency = 1,
LayoutOrder = currLayoutOrder,
}, {
DataButton = Roact.createElement(BannerButton, {
size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
pos = UDim2.new(),
isExpanded = showGraph,
onButtonPress = self.getOnButtonPress(name),
}, {
[name] = Roact.createElement(CellLabel, {
text = name,
size = UDim2.new(1 - VALUE_CELL_WIDTH, -CELL_PADDING - ARROW_PADDING, 1, 0),
pos = UDim2.new(0, CELL_PADDING + ARROW_PADDING, 0, 0),
}),
Data = Roact.createElement(CellLabel, {
text = data.dataSet:back().value,
size = UDim2.new(VALUE_CELL_WIDTH , -CELL_PADDING, 1, 0),
pos = UDim2.new(1 - VALUE_CELL_WIDTH, CELL_PADDING, 0, 0),
}),
VerticalLine = Roact.createElement("Frame", {
Size = UDim2.new(0, LINE_WIDTH, 0, ENTRY_HEIGHT),
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
lowerHorizontalLine = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
}),
Graph = showGraph and Roact.createElement(LineGraph, {
pos = UDim2.new(0, 0, 0, ENTRY_HEIGHT),
size = UDim2.new(1, 0, 1, -ENTRY_HEIGHT),
graphData = data.dataSet,
maxY = data.max,
minY = data.min,
getX = getX,
getY = getY,
axisLabelX = "Timestamp",
axisLabelY = name,
stringFormatX = convertTimeStamp,
stringFormatY = stringFormatY,
}),
})
componentHeight = componentHeight + ENTRY_HEIGHT
end
end
end
if currLayoutOrder == 1 then
if totalCount == 0 then
return Roact.createElement("TextLabel", {
Size = size,
Text = NO_DATA_MSG,
TextColor3 = Constants.Color.Text,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
})
else
local noResultSearchStr = string.format(NO_RESULT_SEARCH_STR, searchTerm)
elements["emptyResult"] = Roact.createElement("TextLabel", {
Size = size,
Text = noResultSearchStr,
TextColor3 = Constants.Color.Text,
BackgroundTransparency = 1,
})
end
end
return Roact.createElement("Frame", {
Size = size,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, {
Header = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, HEADER_HEIGHT),
BackgroundTransparency = 1,
LayoutOrder = 1,
}, {
[HEADER_NAMES[1]] = Roact.createElement(CellLabel, {
text = HEADER_NAMES[1],
size = UDim2.new(1 - VALUE_CELL_WIDTH, -CELL_PADDING - ARROW_PADDING, 1, 0),
pos = UDim2.new(0, CELL_PADDING + ARROW_PADDING, 0, 0),
}),
[HEADER_NAMES[2]] = Roact.createElement(CellLabel, {
text = HEADER_NAMES[2],
size = UDim2.new(VALUE_CELL_WIDTH , -CELL_PADDING, 1, 0),
pos = UDim2.new(1 - VALUE_CELL_WIDTH, CELL_PADDING, 0, 0),
}),
upperHorizontalLine = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
vertical = Roact.createElement("Frame", {
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
Position = UDim2.new(1 - VALUE_CELL_WIDTH, 0, 0, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
lowerHorizontalLine = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
}),
}),
mainFrame = Roact.createElement("ScrollingFrame", {
Position = UDim2.new(0, 0, 0, HEADER_HEIGHT),
Size = UDim2.new(1, 0, 1, -HEADER_HEIGHT),
ScrollBarThickness = 5,
CanvasSize = UDim2.new(1, 0, 0, componentHeight),
BackgroundTransparency = 1,
}, elements),
})
end
return DataConsumer(DataStoresChart, "DataStoresData")
@@ -0,0 +1,16 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DataProvider = require(script.Parent.Parent.DataProvider)
local DataStoresChart = require(script.Parent.DataStoresChart)
it("should create and destroy without errors", function()
local element = Roact.createElement(DataProvider, {}, {
DataStoresChart = Roact.createElement(DataStoresChart)
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,108 @@
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
local Signal = require(script.Parent.Parent.Parent.Signal)
local MAX_DATASET_COUNT = tonumber(settings():GetFVariable("NewDevConsoleMaxGraphCount"))
local getClientReplicator = require(script.Parent.Parent.Parent.Util.getClientReplicator)
local DataStoresData = {}
DataStoresData.__index = DataStoresData
function DataStoresData.new()
local self = {}
setmetatable(self, DataStoresData)
self._dataStoresUpdated = Signal.new()
self._dataStoresData = {}
self._dataStoresDataCount = 0
self._lastUpdateTime = 0
return self
end
function DataStoresData:Signal()
return self._dataStoresUpdated
end
function DataStoresData:getCurrentData()
return self._dataStoresData, self._dataStoresDataCount
end
function DataStoresData:updateValue(key, value)
if not self._dataStoresData[key] then
local newBuffer = CircularBuffer.new(MAX_DATASET_COUNT)
newBuffer:push_back({
value = value,
time = self._lastUpdateTime
})
self._dataStoresData[key] = {
max = value,
min = value,
dataSet = newBuffer,
}
else
local dataEntry = self._dataStoresData[key]
local currMax = dataEntry.max
local currMin = dataEntry.min
local update = {
value = value,
time = self._lastUpdateTime
}
local overwrittenEntry = self._dataStoresData[key].dataSet:push_back(update)
if overwrittenEntry then
local iter = self._dataStoresData[key].dataSet:iterator()
local dat = iter:next()
if currMax == overwrittenEntry.value then
currMax = currMin
while dat do
currMax = dat.value < currMax and currMax or dat.value
dat = iter:next()
end
end
if currMin == overwrittenEntry.value then
currMin = currMax
while dat do
currMin = currMin < dat.value and currMin or dat.value
dat = iter:next()
end
end
end
self._dataStoresData[key].max = currMax < value and value or currMax
self._dataStoresData[key].min = currMin < value and currMin or value
end
end
function DataStoresData:start()
local clientReplicator = getClientReplicator()
if clientReplicator and not self._statsListenerConnection then
self._statsListenerConnection = clientReplicator.StatsReceived:connect(function(stats)
local dataStoreBudget = stats.DataStoreBudget
self._lastUpdateTime = os.time()
if dataStoreBudget then
local count = 0
for k, v in pairs(dataStoreBudget) do
if type(v) == 'number' then
self:updateValue(k,v)
count = count + 1
end
end
self._dataStoresDataCount = count
self._dataStoresUpdated:Fire(self._dataStoresData, self._dataStoresDataCount)
end
end)
clientReplicator:RequestServerStats(true)
end
end
function DataStoresData:stop()
-- listeners are responsible for disconnecting themselves
if self._statsListenerConnection then
self._statsListenerConnection:Disconnect()
self._statsListenerConnection = nil
end
end
return DataStoresData
@@ -0,0 +1,105 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Components = script.Parent.Parent.Parent.Components
local DataStoresChart = require(Components.DataStores.DataStoresChart)
local UtilAndTab = require(Components.UtilAndTab)
local Actions = script.Parent.Parent.Parent.Actions
local DataStoresUpdateSearchFilter = require(Actions.DataStoresUpdateSearchFilter)
local Constants = require(script.Parent.Parent.Parent.Constants)
local PADDING = Constants.GeneralFormatting.MainRowPadding
local MainViewDataStores = Roact.PureComponent:extend("MainViewDataStores")
function MainViewDataStores:init()
self.onUtilTabHeightChanged = function(utilTabHeight)
self:setState({
utilTabHeight = utilTabHeight
})
end
self.onSearchTermChanged = function(newSearchTerm)
self.props.dispatchDataStoresUpdateSearchFilter(newSearchTerm, {})
end
self.utilRef = Roact.createRef()
self.state = {
utilTabHeight = 0
}
end
function MainViewDataStores:didMount()
local utilSize = self.utilRef.current.Size
self:setState({
utilTabHeight = utilSize.Y.Offset
})
end
function MainViewDataStores:didUpdate()
local utilSize = self.utilRef.current.Size
if utilSize.Y.Offset ~= self.state.utilTabHeight then
self:setState({
utilTabHeight = utilSize.Y.Offset
})
end
end
function MainViewDataStores:render()
local size = self.props.size
local formFactor = self.props.formFactor
local tabList = self.props.tabList
local searchTerm = self.props.storesSearchTerm
local utilTabHeight = self.state.utilTabHeight
return Roact.createElement("Frame", {
Size = size,
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
LayoutOrder = 3,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, PADDING),
SortOrder = Enum.SortOrder.LayoutOrder,
}),
UtilAndTab = Roact.createElement(UtilAndTab, {
windowWidth = size.X.Offset,
formFactor = formFactor,
tabList = tabList,
searchTerm = searchTerm,
layoutOrder = 1,
refForParent = self.utilRef,
onHeightChanged = self.onUtilTabHeightChanged,
onSearchTermChanged = self.onSearchTermChanged,
}),
DataStores = utilTabHeight > 0 and Roact.createElement(DataStoresChart, {
size = UDim2.new(1, 0, 1, -utilTabHeight),
searchTerm = searchTerm,
layoutOrder = 2,
})
})
end
local function mapStateToProps(state, props)
return {
storesSearchTerm = state.DataStoresData.storesSearchTerm,
}
end
local function mapDispatchToProps(dispatch)
return {
dispatchDataStoresUpdateSearchFilter = function(searchTerm, filters)
dispatch(DataStoresUpdateSearchFilter(searchTerm, filters))
end,
}
end
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(MainViewDataStores)
@@ -0,0 +1,36 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Store = require(CorePackages.Rodux).Store
local DataProvider = require(script.Parent.Parent.DataProvider)
local MainViewDataStores = require(script.Parent.MainViewDataStores)
it("should create and destroy without errors", function()
local store = Store.new(function()
return {
MainView = {
currTabIndex = 0
},
DataStoresData = {
storesSearchTerm = ""
}
}
end)
local element = Roact.createElement(RoactRodux.StoreProvider, {
store = store,
}, {
DataProvider = Roact.createElement(DataProvider, {}, {
MainViewDataStores = Roact.createElement(MainViewDataStores, {
size = UDim2.new(),
tabList = {},
})
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,184 @@
local CorePackages = game:GetService("CorePackages")
local RobloxGui = game:GetService("CoreGui").RobloxGui
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Constants = require(script.Parent.Parent.Constants)
local FRAME_HEIGHT = Constants.TopBarFormatting.FrameHeight
local ICON_SIZE = .5 * FRAME_HEIGHT
local ICON_PADDING = (FRAME_HEIGHT - ICON_SIZE) / 2
local DEVCONSOLE_TEXT = "Developer Console"
local DEVCONSOLE_TEXT_X_PADDING = 4
local DEVCONSOLE_TEXT_FRAMESIZE = TextService:GetTextSize(DEVCONSOLE_TEXT, Constants.DefaultFontSize.TopBar,
Constants.Font.TopBar, Vector2.new(0, 0))
local LiveUpdateElement = require(script.Parent.Parent.Components.LiveUpdateElement)
local SetDevConsolePosition = require(script.Parent.Parent.Actions.SetDevConsolePosition)
local DevConsoleTopBar = Roact.Component:extend("DevConsoleTopBar")
function DevConsoleTopBar:init()
self.inputBegan = function(rbx,input)
if self.props.isMinimized then
return
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
local absPos = self.ref.current.AbsolutePosition
local startPos = Vector3.new(absPos.X, absPos.Y, 0)
self:setState({
startPos = startPos,
startOffset = input.Position,
moving = true,
})
end
end
self.inputChanged = function(rbx,input)
if self.state.moving then
local offset = self.state.startPos - self.state.startOffset
offset = offset + input.Position
local position = UDim2.new(0, offset.X, 0, offset.Y)
self.props.dispatchSetDevConsolePosition(position)
end
end
self.inputEnded = function(rbx,input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
self:setState({
moving = false,
})
end
end
self.ref = Roact.createRef()
end
function DevConsoleTopBar:render()
local isMinimized = self.props.isMinimized
local formFactor = self.props.formFactor
local onMinimizeClicked = self.props.onMinimizeClicked
local onMaximizeClicked = self.props.onMaximizeClicked
local onCloseClicked = self.props.onCloseClicked
local moving = self.state.moving
local elements = {}
elements["WindowTitle"] = Roact.createElement("TextLabel", {
Text = DEVCONSOLE_TEXT,
TextSize = Constants.DefaultFontSize.TopBar,
TextColor3 = Color3.new(1, 1, 1),
Font = Constants.Font.TopBar,
Size = UDim2.new(0, DEVCONSOLE_TEXT_FRAMESIZE.X, 0, FRAME_HEIGHT),
Position = UDim2.new(0, DEVCONSOLE_TEXT_X_PADDING, 0, 0),
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
})
local liveStatsModulePos = UDim2.new(0, DEVCONSOLE_TEXT_FRAMESIZE.X, 0, 0)
local liveStatsModuleSize = UDim2.new(1, -2 * DEVCONSOLE_TEXT_FRAMESIZE.X, 0, FRAME_HEIGHT)
if isMinimized then
liveStatsModulePos = UDim2.new(0, 0, 1, 0)
liveStatsModuleSize = UDim2.new(1, 0, 1, 0)
elseif self.ref.current then
liveStatsModuleSize = UDim2.new(
0,
self.ref.current.AbsoluteSize.X - (2 * DEVCONSOLE_TEXT_FRAMESIZE.X),
0,
FRAME_HEIGHT
)
end
local topBarLiveUpdate = self.props.topBarLiveUpdate
elements["LiveStatsModule"] = Roact.createElement(LiveUpdateElement, {
topBarLiveUpdate = topBarLiveUpdate,
size = liveStatsModuleSize,
position = liveStatsModulePos,
})
-- minimize and maximize buttons should only appear on desktop
if formFactor == Constants.FormFactor.Large then
if not isMinimized then
elements["MinButton"] = Roact.createElement("ImageButton", {
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
Position = UDim2.new(1, -2 * FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
BorderColor3 = Color3.new(1, 0, 0),
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
Image = Constants.Image.Minimize,
[Roact.Event.Activated] = onMinimizeClicked,
})
else
elements["MaxButton"] = Roact.createElement("ImageButton", {
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
Position = UDim2.new(1, -2 * FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
BorderColor3 = Color3.new(0, 0, 1),
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
Image = Constants.Image.Maximize,
[Roact.Event.Activated] = onMaximizeClicked,
})
end
end
elements["CloseButton"] = Roact.createElement("ImageButton", {
Size = UDim2.new(0, ICON_SIZE, 0, ICON_SIZE),
Position = UDim2.new(1, -FRAME_HEIGHT + ICON_PADDING, 0, ICON_PADDING),
BorderColor3 = Color3.new(0, 1, 0),
BackgroundColor3 = Constants.Color.BaseGray,
BackgroundTransparency = 1,
Image = Constants.Image.Close,
[Roact.Event.Activated] = onCloseClicked,
})
--[[ we do this to catch all inputchanged events
if we can handle LARGE distances of continuous MouseMovmement input events
for dragging then we might be able to remove the portal
]]--
elements["MovmentCatchAll"] = moving and Roact.createElement(Roact.Portal, {
target = RobloxGui,
}, {
InputCatcher = Roact.createElement("ScreenGui", {}, {
GreyOutFrame = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = Constants.Color.Black,
BackgroundTransparency = .99,
Active = true,
[Roact.Event.InputChanged] = self.inputChanged,
[Roact.Event.InputEnded] = self.inputEnded,
})
})
})
return Roact.createElement("ImageButton", {
Size = UDim2.new(1, 0, 0, FRAME_HEIGHT),
BackgroundColor3 = Constants.Color.Black,
BackgroundTransparency = .5,
AutoButtonColor = false,
LayoutOrder = 1,
[Roact.Ref] = self.ref,
[Roact.Event.InputBegan] = self.inputBegan,
}, elements)
end
local function mapDispatchToProps(dispatch)
return {
dispatchSetDevConsolePosition = function (size)
dispatch(SetDevConsolePosition(size))
end
}
end
return RoactRodux.UNSTABLE_connect2(nil, mapDispatchToProps)(DevConsoleTopBar)
@@ -0,0 +1,32 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Store = require(CorePackages.Rodux).Store
local DataProvider = require(script.Parent.DataProvider)
local DevConsoleTopBar = require(script.Parent.DevConsoleTopBar)
it("should create and destroy without errors", function()
local store = Store.new(function()
return {
TopBarLiveUpdate = {
LogWarningCount = 0,
LogErrorCount = 0
}
}
end)
local element = Roact.createElement(RoactRodux.StoreProvider, {
store = store,
}, {
DataProvider = Roact.createElement(DataProvider, nil, {
DevConsoleTopBar = Roact.createElement(DevConsoleTopBar)
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,317 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui").RobloxGui
local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")
local setMouseVisibility = require(script.Parent.Parent.Util.setMouseVisibility)
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local DevConsole = script.Parent.Parent
local Constants = require(DevConsole.Constants)
local TOPBAR_HEIGHT = Constants.TopBarFormatting.FrameHeight
local ROW_PADDING = Constants.Padding.WindowPadding
local MIN_SIZE = Constants.MainWindowInit.MinSize
local Components = DevConsole.Components
local DevConsoleTopBar = require(Components.DevConsoleTopBar)
local Actions = script.Parent.Parent.Actions
local ChangeDevConsoleSize = require(Actions.ChangeDevConsoleSize)
local SetDevConsoleVisibility = require(Actions.SetDevConsoleVisibility)
local SetDevConsoleMinimized = require(Actions.SetDevConsoleMinimized)
local BORDER_SIZE = 16
local DevConsoleWindow = Roact.PureComponent:extend("DevConsoleWindow")
function DevConsoleWindow:onMinimizeClicked()
self.props.dispatchSetDevConsoleMinimized(true)
end
function DevConsoleWindow:onMaximizeClicked()
self.props.dispatchSetDevConsoleMinimized(false)
end
function DevConsoleWindow:onCloseClicked()
self.props.dispatchSetDevConsolVisibility(false)
end
function DevConsoleWindow:init()
self.setDevConsoleSize = function (self, topLeft, bottomRight)
local x = bottomRight.X - topLeft.X
local y = bottomRight.Y - topLeft.Y
x = x < MIN_SIZE.X and MIN_SIZE.X or x
y = y < MIN_SIZE.Y and MIN_SIZE.Y or y
self.props.dispatchChangeDevConsoleSize(UDim2.new(0, x, 0, y))
end
self.resizeInputBegan = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
self:setState({
resizing = true,
})
end
end
self.resizeInputChanged = function(rbx,input)
if self.state.resizing then
local currPosition = self.ref.current.AbsolutePosition
local cornerPos = input.Position
self:setDevConsoleSize(currPosition, cornerPos)
end
end
self.resizeInputEnded = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 then
--reset resize-dragger
self:setState({
resizing = false
})
end
end
self.doGamepadMenuButton = function(input)
if self.props.isVisible then
local keyToListenTo = input.KeyCode == Enum.KeyCode.ButtonStart or input.KeyCode == Enum.KeyCode.Escape
if keyToListenTo then
self.props.dispatchSetDevConsolVisibility(false)
end
end
end
self.ref = Roact.createRef()
self.state = {
resizing = false,
}
end
function DevConsoleWindow:didMount()
-- we need to run this delay before grabbing the size of the frame
-- because the DevconsoleWindow is mounted before the ScreenGui
-- is resized to the correct screen size.
delay(0, function ()
if self.ref.current then
local absPos1 = self.ref.current.AbsolutePosition
local absPos2 = self.ref.current.ResizeButton.AbsolutePosition
self:setDevConsoleSize(absPos1, absPos2)
end
end)
setMouseVisibility(self.props.isVisible)
self.gamepadMenuListener = UserInputService.InputBegan:Connect(self.doGamepadMenuButton)
end
function DevConsoleWindow:willUnmount()
if GuiService.SelectedCoreObject == self.ref.current then
GuiService.SelectedCoreObject = nil
end
self.gamepadMenuListener:Disconnect()
end
function DevConsoleWindow:didUpdate(previousProps, previousState)
setMouseVisibility(self.props.isVisible)
if self.props.isMinimized and (GuiService.SelectedCoreObject == self.ref.current) then
GuiService.SelectedCoreObject = nil
elseif self.props.isVisible ~= previousProps.isVisible or
self.props.currTabIndex ~= previousProps.currTabIndex then
local inputTypeEnum = UserInputService:GetLastInputType()
local isGamepad = (Enum.UserInputType.Gamepad1 == inputTypeEnum)
if isGamepad and self.props.isVisible then
GuiService.SelectedCoreObject = self.ref.current
else
GuiService.SelectedCoreObject = nil
end
end
end
function DevConsoleWindow:render()
local isVisible = self.props.isVisible
local formFactor = self.props.formFactor
local isdeveloperView = self.props.isdeveloperView
local currTabIndex = self.props.currTabIndex
local tabList = self.props.tabList
local isMinimized = self.props.isMinimized
local pos = self.props.position
local size = self.props.size
local resizing = self.state.resizing
local windowSize = size
local windowPos = UDim2.new()
local elements = {}
local borderSizePixel = BORDER_SIZE
if formFactor ~= Constants.FormFactor.Large then
-- none desktop/Large are full screen devconsoles
local absSize = CoreGui.AbsoluteSize
size = UDim2.new(0, absSize.X, 0, absSize.Y)
pos = UDim2.new(0, 0, 0, 0)
windowPos = UDim2.new(0, 16, 0, 0)
windowSize = size + UDim2.new(0, -32, 0, 0)
borderSizePixel = 0
end
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder,
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
Padding = UDim.new(0, ROW_PADDING),
})
if isMinimized then
elements["TopBar"] = Roact.createElement(DevConsoleTopBar, {
LayoutOrder = 1,
formFactor = formFactor,
isMinimized = true,
onMinimizeClicked = function()
self:onMinimizeClicked()
end,
onMaximizeClicked = function()
self:onMaximizeClicked()
end,
onCloseClicked = function()
self:onCloseClicked()
end,
})
return Roact.createElement("Frame", {
Position = UDim2.new(1, -500, 1, -2 * TOPBAR_HEIGHT),
Size = UDim2.new(0, 500, 0, 2 * TOPBAR_HEIGHT),
BackgroundColor3 = Color3.new(0, 0, 0),
Transparency = Constants.MainWindowInit.Transparency,
Active = true,
AutoLocalize = false,
Visible = isVisible,
Selectable = true,
BorderColor3 = Constants.Color.BaseGray,
[Roact.Ref] = self.ref,
}, elements)
else
elements["TopBar"] = Roact.createElement(DevConsoleTopBar, {
LayoutOrder = 1,
formFactor = formFactor,
isMinimized = false,
onMinimizeClicked = function()
self:onMinimizeClicked()
end,
onMaximizeClicked = function()
self:onMaximizeClicked()
end,
onCloseClicked = function()
self:onCloseClicked()
end,
})
local mainViewSize = windowSize
local TopSectionHeight = TOPBAR_HEIGHT + 2 * ROW_PADDING
local mainViewSizeOffset = UDim2.new(0, 0, 0, TopSectionHeight)
mainViewSize = mainViewSize - mainViewSizeOffset
if self.ref.current and isVisible and tabList then
local targetTab = tabList[currTabIndex]
if targetTab then
elements["MainView"] = Roact.createElement( targetTab.tab, {
size = mainViewSize,
formFactor = formFactor,
isdeveloperView = isdeveloperView,
tabList = tabList,
})
end
end
return Roact.createElement("Frame", {
Position = pos,
Size = size,
Visible = isVisible,
BackgroundColor3 = Color3.new(0, 0, 0),
Transparency = Constants.MainWindowInit.Transparency,
BorderColor3 = Constants.Color.BaseGray,
BorderSizePixel = borderSizePixel,
Active = true,
AutoLocalize = false,
Selectable = false,
[Roact.Ref] = self.ref,
}, {
DevConsoleUI = Roact.createElement("Frame", {
Size = windowSize,
Position = windowPos,
BackgroundTransparency = 1,
},elements),
ResizeButton = Roact.createElement("ImageButton", {
Position = UDim2.new(1, 0, 1, 0),
Size = UDim2.new(0, borderSizePixel, 0, borderSizePixel),
BackgroundColor3 = Color3.new(0, 0, 0),
Modal = true,
[Roact.Event.InputBegan] = self.resizeInputBegan,
}),
--[[ we do this to catch all inputchanged events
if we can handle LARGE distances of continuous MouseMovmement input events
for dragging then we might be able to remove the portal
]]--
ResizeCatchAll = resizing and Roact.createElement(Roact.Portal, {
target = CoreGui,
}, {
InputCatcher = Roact.createElement("ScreenGui", {}, {
GreyOutFrame = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = Constants.Color.Black,
BackgroundTransparency = .99,
Active = true,
[Roact.Event.InputChanged] = self.resizeInputChanged,
[Roact.Event.InputEnded] = self.resizeInputEnded,
})
})
})
})
end
end
local function mapStateToProps(state, props)
return {
isVisible = state.DisplayOptions.isVisible,
isMinimized = state.DisplayOptions.isMinimized,
position = state.DisplayOptions.position,
size = state.DisplayOptions.size,
currTabIndex = state.MainView.currTabIndex,
tabList = state.MainView.tabList,
}
end
local function mapDispatchToProps(dispatch)
return {
dispatchChangeDevConsoleSize = function (size)
dispatch(ChangeDevConsoleSize(size))
end,
dispatchSetDevConsolVisibility = function (isVisible)
dispatch(SetDevConsoleVisibility(isVisible))
end,
dispatchSetDevConsoleMinimized = function (isMinimized)
dispatch(SetDevConsoleMinimized(isMinimized))
end,
}
end
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(DevConsoleWindow)
@@ -0,0 +1,51 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Store = require(CorePackages.Rodux).Store
local DataProvider = require(script.Parent.DataProvider)
local DevConsoleWindow = require(script.Parent.DevConsoleWindow)
it("should create and destroy without errors", function()
local store = Store.new(function()
return {
DisplayOptions = {
isVisible = 0,
platform = 0,
isMinimized = false,
size = UDim2.new(1, 0, 1, 0),
},
MainView = {
currTabIndex = 0
},
TopBarLiveUpdate = {
LogWarningCount = 0,
LogErrorCount = 0
},
LogData = {
clientData = { },
clientDataFiltered = { },
clientSearchTerm = "",
clientTypeFilters = { },
serverData = { },
serverDataFiltered = { },
serverSearchTerm = "",
serverTypeFilters = { },
}
}
end)
local element = Roact.createElement(RoactRodux.StoreProvider, {
store = store,
}, {
DataProvider = Roact.createElement(DataProvider, {}, {
DevConsoleWindow = Roact.createElement(DevConsoleWindow)
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,154 @@
local CorePackages = game:GetService("CorePackages")
local RobloxGui = game:GetService("CoreGui").RobloxGui
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local FONT = Constants.Font.UtilBar
local FONT_SIZE = Constants.DefaultFontSize.UtilBar
local ARROW_SIZE = Constants.GeneralFormatting.DropDownArrowHeight
local ARROW_OFFSET = ARROW_SIZE / 2
local OPEN_ARROW = Constants.Image.DownArrow
local INNER_FRAME_PADDING = 12
local DropDown = Roact.Component:extend("DropDown")
function DropDown:init()
self.onMainButtonPressed = function(rbx, input)
self:setState({
showDropDown = true,
})
end
self.nonDropDownSelection = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
(input.UserInputType == Enum.UserInputType.Touch and
input.UserInputState == Enum.UserInputState.End) then
self:setState({
showDropDown = false
})
end
end
self.state = {
showDropDown = false,
}
self.ref = Roact.createRef()
end
function DropDown:render()
local buttonSize = self.props.buttonSize
local dropDownList = self.props.dropDownList
local selectedIndex = self.props.selectedIndex
local onSelection = self.props.onSelection
local layoutOrder = self.props.layoutOrder
local showDropDown = self.ref.current and self.state.showDropDown
local children = {}
local absolutePosition
local outerFrameSize
local frameHeight = 0
local frameWidth = 0
if self.ref.current and showDropDown then
local absolutePos = self.ref.current.AbsolutePosition
local absoluteSize = self.ref.current.AbsoluteSize
frameWidth = absoluteSize.X
children["UIListLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
})
for ind, name in pairs(dropDownList) do
local color = (ind == selectedIndex) and Constants.Color.SelectedGray or Constants.Color.UnselectedGray
children[name] = Roact.createElement("TextButton", {
Size = buttonSize,
Text = name,
TextColor3 = Constants.Color.Text,
TextSize = FONT_SIZE,
Font = FONT,
AutoButtonColor = false,
BackgroundColor3 = color,
BackgroundTransparency = 0,
BorderSizePixel = 0,
LayoutOrder = ind,
[Roact.Event.Activated] = function()
onSelection(ind)
self:setState({
showDropDown = false
})
end
})
frameHeight = frameHeight + absoluteSize.Y
end
local padding = 2 * INNER_FRAME_PADDING
outerFrameSize = UDim2.new(0, frameWidth + padding, 0, frameHeight + padding)
absolutePosition = UDim2.new(0, absolutePos.X, 0, absolutePos.Y + absoluteSize.Y)
end
return Roact.createElement("TextButton", {
Size = buttonSize,
Text = dropDownList[selectedIndex],
TextColor3 = Constants.Color.Text,
TextSize = FONT_SIZE,
Font = FONT,
AutoButtonColor = false,
BackgroundColor3 = Constants.Color.UnselectedGray,
BackgroundTransparency = 0,
LayoutOrder = layoutOrder,
[Roact.Event.Activated] = self.onMainButtonPressed,
[Roact.Ref] = self.ref,
}, {
arrow = Roact.createElement("ImageLabel", {
Image = OPEN_ARROW,
BackgroundTransparency = 1,
Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE),
Position = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, .5, -ARROW_OFFSET),
}),
DropDown = showDropDown and Roact.createElement(Roact.Portal, {
target = RobloxGui,
}, {
FullScreen = Roact.createElement("ScreenGui", {
}, {
InputCatcher = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, 0, 0, 0),
BackgroundTransparency = 1,
[Roact.Event.InputEnded] = self.nonDropDownSelection,
}, {
OuterFrame = Roact.createElement("ImageButton", {
Size = outerFrameSize,
AutoButtonColor = false,
Position = absolutePosition,
BackgroundColor3 = Constants.Color.TextBoxGray,
BackgroundTransparency = 0,
}, {
innerFrame = Roact.createElement("Frame", {
Position = UDim2.new(0, INNER_FRAME_PADDING, 0 , INNER_FRAME_PADDING),
Size = UDim2.new(0, frameWidth, 0, frameHeight),
BackgroundTransparency = 1,
}, children)
})
})
})
})
})
end
return DropDown
@@ -0,0 +1,14 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DropDown = require(script.Parent.DropDown)
it("should create and destroy without errors", function()
local element = Roact.createElement(DropDown, {
dropDownList = {},
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,153 @@
local CorePackages = game:GetService("CorePackages")
local CoreGui = game:GetService("CoreGui").RobloxGui
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local ENTRY_HEIGHT = Constants.GeneralFormatting.DropDownEntryHeight
local ARROW_SIZE = Constants.GeneralFormatting.DropDownArrowHeight
local ARROW_OFFSET = ARROW_SIZE / 2
local OPEN_ARROW = Constants.Image.DownArrow
local FULL_SCREEN_WIDTH = 375
local INNER_Y_OFFSET = 8
local INNER_X_OFFSET = 15
local FullScreenDropDownButton = Roact.Component:extend("FullScreenDropDownButton")
function FullScreenDropDownButton:init()
self.startDropDownView = function()
self:setState({
selectionScreenExpanded = true
})
end
self.noSelection = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or
(input.UserInputType == Enum.UserInputType.Touch and
input.UserInputState == Enum.UserInputState.End) then
self:setState({
selectionScreenExpanded = false
})
end
end
self.state = {
selectionScreenExpanded = false,
}
end
function FullScreenDropDownButton:render()
local buttonSize = self.props.buttonSize
local dropDownList = self.props.dropDownList
local selectedIndex = self.props.selectedIndex
local onSelection = self.props.onSelection
local layoutOrder = self.props.layoutOrder
local isSelecting = self.state.selectionScreenExpanded
local dropDownItemList = {}
local scrollingFrameHeight = 2 * INNER_Y_OFFSET
if isSelecting then
dropDownItemList["UIListLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirection = Enum.FillDirection.Vertical,
})
if dropDownList then
for ind,name in ipairs(dropDownList) do
local color = (ind == selectedIndex) and Constants.Color.SelectedGray or Constants.Color.UnselectedGray
dropDownItemList[ind] = Roact.createElement("TextButton", {
Text = name,
Font = Constants.Font.TabBar,
TextSize = Constants.DefaultFontSize.DropDownTabBar,
TextColor3 = Constants.Color.Text,
AutoButtonColor = false,
Size = UDim2.new(1, 0, 0, ENTRY_HEIGHT),
BackgroundColor3 = color,
LayoutOrder = ind,
BorderSizePixel = 0,
[Roact.Event.Activated] = function(rbx)
self:setState({
selectionScreenExpanded = false
})
onSelection(ind)
end,
})
scrollingFrameHeight = scrollingFrameHeight + ENTRY_HEIGHT
end
end
end
return Roact.createElement("TextButton", {
Size = buttonSize,
BackgroundColor3 = Constants.Color.UnselectedGray,
Text = "",
AutoButtonColor = false,
LayoutOrder = layoutOrder,
[Roact.Event.Activated] = self.startDropDownView,
}, {
text = Roact.createElement("TextLabel", {
Size = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, 1, 0),
Text = dropDownList[selectedIndex],
Font = Constants.Font.TabBar,
TextSize = Constants.DefaultFontSize.DropDownTabBar,
TextXAlignment = Enum.TextXAlignment.Center,
TextColor3 = Constants.Color.Text,
BackgroundTransparency = 1,
}),
arrow = Roact.createElement("ImageLabel", {
Image = OPEN_ARROW,
BackgroundTransparency = 1,
Size = UDim2.new(0, ARROW_SIZE, 0, ARROW_SIZE),
Position = UDim2.new(1, -ARROW_SIZE - ARROW_OFFSET, .5, -ARROW_OFFSET),
}),
selectionView = isSelecting and Roact.createElement(Roact.Portal, {
target = CoreGui,
}, {
TempScreen = Roact.createElement("ScreenGui", {}, {
GreyOutFrame = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundColor3 = Constants.Color.Black,
BackgroundTransparency = .36,
Active = true,
[Roact.Event.InputEnded] = self.noSelection,
}, {
BorderFrame = Roact.createElement("Frame", {
Size = UDim2.new(0, FULL_SCREEN_WIDTH, 0, scrollingFrameHeight),
Position = UDim2.new(.5, -FULL_SCREEN_WIDTH / 2, 0, 0),
BackgroundColor3 = Constants.Color.UnselectedGray,
BorderSizePixel = 0,
}, {
SelectionFrame = Roact.createElement("ScrollingFrame", {
Size = UDim2.new(1, -2 * INNER_X_OFFSET, 1, -2 * INNER_Y_OFFSET),
Position = UDim2.new(0, INNER_X_OFFSET, 0, INNER_Y_OFFSET),
BackgroundTransparency = 1,
-- adding an extra entry's worth of height for easier access to last
-- child when trying to select last child
CanvasSize = UDim2.new(1, -2 * INNER_X_OFFSET, 1, ENTRY_HEIGHT),
BorderSizePixel = 0,
ScrollBarThickness = 0,
}, dropDownItemList)
})
})
})
})
})
end
return FullScreenDropDownButton
@@ -0,0 +1,14 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local FullScreenDropDownButton = require(script.Parent.FullScreenDropDownButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(FullScreenDropDownButton, {
dropDownList = {}
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,34 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local FONT = Constants.Font.MainWindowHeader
local TEXT_SIZE = Constants.DefaultFontSize.MainWindowHeader
local TEXT_COLOR = Constants.Color.Text
local function HeaderButton(props)
local text = props.text
local size = props.size
local pos = props.pos
local sortfunction = props.sortfunction
return Roact.createElement("TextButton", {
Text = text,
TextSize = TEXT_SIZE,
TextColor3 = TEXT_COLOR,
Font = FONT,
TextXAlignment = Enum.TextXAlignment.Left,
Size = size,
Position = pos,
BackgroundTransparency = 1,
[Roact.Event.Activated] = function()
if sortfunction then
sortfunction(text)
end
end,
})
end
return HeaderButton
@@ -0,0 +1,305 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LineGraphHoverDisplay = require(script.Parent.LineGraphHoverDisplay)
local Constants = require(script.Parent.Parent.Constants)
local TEXT_COLOR = Constants.Color.Text
local MAIN_LINE_COLOR = Constants.Color.HighlightBlue
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
local LINE_COLOR = Constants.GeneralFormatting.LineColor
local POINT_WIDTH = Constants.Graph.PointWidth
local POINT_OFFSET = Constants.Graph.PointOffset
local GRAPH_PADDING = Constants.Graph.Padding
local GRAPH_SCALE = Constants.Graph.Scale
local GRAPH_Y_INNER_PADDING = Constants.Graph.InnerPaddingY
local GRAPH_Y_INNER_SCALE = Constants.Graph.InnerScaleY
local TEXT_PADDING = Constants.Graph.TextPadding
local INVIS_LINE_THRESHOLD = 10
local LineGraph = Roact.Component:extend("LineGraph")
function LineGraph:init()
self.onGraphInputChanged = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
if not self.state.holdPos then
self:setState({
inputPosition = input.Position,
})
end
end
end
self.onGraphInputEnded = function(rbx, input)
if input.UserInputType == Enum.UserInputType.MouseMovement then
if not self.state.holdPos then
self:setState({
inputPosition = false
})
end
elseif input.UserInputType == Enum.UserInputType.MouseButton1 then
self:setState({
holdPos = not self.state.holdPos
})
end
end
self.graphRef = Roact.createRef()
self.state = {
selectedTimeStamps = {}
}
end
function LineGraph:didUpdate()
if self.state.absGraphSize ~= self.graphRef.current.AbsoluteSize then
local absSize = self.graphRef.current.AbsoluteSize
local absPos = self.graphRef.current.AbsolutePosition
self:setState({
absGraphSize = absSize,
absGraphPos = absPos,
})
end
end
function LineGraph:didMount()
local absSize = self.graphRef.current.AbsoluteSize
local absPos = self.graphRef.current.AbsolutePosition
self:setState({
absGraphSize = absSize,
absGraphPos = absPos,
})
end
function LineGraph:render()
local size = self.props.size
local pos = self.props.pos
local graphData = self.props.graphData
local getX = self.props.getX
local getY = self.props.getY
local stringFormatX = self.props.stringFormatX
local stringFormatY = self.props.stringFormatY
local axisLabelX = self.props.axisLabelX
local axisLabelY = self.props.axisLabelY
local layoutOrder = self.props.layoutOrder
local inputPosition = self.state.inputPosition
local absGraphSize = self.state.absGraphSize
local absGraphPos = self.state.absGraphPos
local maxX = getX(graphData:back())
local minX = getX(graphData:front())
local maxY = self.props.maxY
local minY = self.props.minY
local hoverLineY
local elements = {}
if absGraphSize then
local dataPoints = {}
local dataIter = graphData:iterator()
local data = dataIter:next()
while data do
local datapoint = getY(data)
local time = getX(data)
local xdivisor = maxX - minX
local xPosition = xdivisor > 0 and (time - minX) / xdivisor or 0
local ydivisor = maxY - minY
local yPosition = ydivisor > 0 and (datapoint - minY) / ydivisor or 1
local point = {
X = xPosition,
Y = yPosition,
data = data,
}
table.insert(dataPoints, point)
data = dataIter:next()
end
for i = 2, #dataPoints do
local aX = dataPoints[i].X * absGraphSize.X
local aY = dataPoints[i].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
local bX = dataPoints[i - 1].X * absGraphSize.X
local bY = dataPoints[i - 1].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
if aX ~= bX then
local vecPosX = (aX + bX) / 2
local vecPosY = (aY + bY) / 2
local vecX = aX - bX
local vecY = aY - bY
local length = math.sqrt((vecX * vecX) + (vecY * vecY))
local rot = math.deg(math.atan2(vecY, vecX))
table.insert(elements, Roact.createElement("Frame", {
Size = UDim2.new(0, length, 0, LINE_WIDTH),
Position = UDim2.new(0, vecPosX - length / 2, 1 - GRAPH_Y_INNER_PADDING, -vecPosY),
BackgroundColor3 = MAIN_LINE_COLOR,
BorderSizePixel = 0,
Rotation = -rot,
}))
table.insert(elements, Roact.createElement("Frame", {
Size = UDim2.new(0, POINT_WIDTH, 0, POINT_WIDTH),
Position = UDim2.new(0, aX, 1 - GRAPH_Y_INNER_PADDING, -aY - POINT_OFFSET),
BackgroundColor3 = MAIN_LINE_COLOR,
BorderSizePixel = 0,
}))
if inputPosition then
local hoverLineX = inputPosition.X - absGraphPos.X
if hoverLineX < aX and bX < hoverLineX then
local aDataX = getX(dataPoints[i].data)
local bDataX = getX(dataPoints[i - 1].data)
local aDataY = getY(dataPoints[i].data)
local bDataY = getY(dataPoints[i - 1].data)
local ratio = (hoverLineX - bX) / vecX
hoverLineY = bY + (vecY * ratio)
local hoverValX = (aDataX - bDataX) * ratio + bDataX
local hoverValY = (aDataY - bDataY) * ratio + bDataY
elements["HoverDetails"] = Roact.createElement(LineGraphHoverDisplay, {
hoverLineX = hoverLineX,
hoverLineY = hoverLineY,
hoverValX = hoverValX,
hoverValY = hoverValY,
stringFormatX = stringFormatX,
stringFormatY = stringFormatY,
})
end
end
end
end
if #dataPoints > 0 then
local lastEntryHeight = dataPoints[#dataPoints].Y * absGraphSize.Y * GRAPH_Y_INNER_SCALE
local currValue = getY(dataPoints[#dataPoints].data)
-- calc if the hovered Y position is very close to the last input entry (within the invis-threshold).
-- If it's NOT within the "invis threshold" then we can show the line and "last entry value"
local showCurrValue = not (hoverLineY and math.abs(lastEntryHeight - hoverLineY) < INVIS_LINE_THRESHOLD)
-- calc if the hovered Y position is very close to the lower Y bound value (within the invis-threshold).
-- If it's NOT within the "invis threshold" then we can show the lowerbound Y value
local hoverLineCheck = (hoverLineY and math.abs(hoverLineY) < INVIS_LINE_THRESHOLD)
local showLeastValue = not (hoverLineCheck or math.abs(lastEntryHeight) < INVIS_LINE_THRESHOLD)
if showCurrValue then
elements["LatestEntryLine"] = Roact.createElement("Frame", {
Size = UDim2.new(1, TEXT_PADDING, 0, LINE_WIDTH),
Position = UDim2.new(0, -TEXT_PADDING, 1 - GRAPH_Y_INNER_PADDING, -lastEntryHeight),
BackgroundColor3 = LINE_COLOR,
BackgroundTransparency = .5,
BorderSizePixel = 0,
})
elements["LatestEntryText"] = Roact.createElement("TextLabel", {
Text = stringFormatY and stringFormatY(currValue) or currValue,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Right,
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING, -lastEntryHeight),
BackgroundTransparency = 1,
})
end
if showLeastValue then
elements["AxisTextY0"] = Roact.createElement("TextLabel", {
Text = stringFormatY and stringFormatY(minY) or minY,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Right,
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING,0),
BackgroundTransparency = 1,
})
end
elements["AxisX"] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, LINE_WIDTH),
Position = UDim2.new(0, 0, 1, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
elements["AxisY"] = Roact.createElement("Frame", {
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
BackgroundColor3 = LINE_COLOR,
BorderSizePixel = 0,
})
end
end
local axisTextPadding = 2 * TEXT_PADDING + 2
return Roact.createElement("Frame", {
Size = size,
Position = pos,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, {
name = Roact.createElement("TextLabel", {
Text = axisLabelY,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING, -TEXT_PADDING),
BackgroundTransparency = 1,
}),
minX = Roact.createElement("TextLabel", {
Text = stringFormatX and stringFormatX(minX) or minX,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Center,
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
BackgroundTransparency = 1,
}),
maxX = Roact.createElement("TextLabel", {
Text = stringFormatX and stringFormatX(maxX) or maxX,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Center,
Position = UDim2.new(GRAPH_PADDING + GRAPH_SCALE, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
BackgroundTransparency = 1,
}),
axisLabelX = Roact.createElement("TextLabel", {
Text = axisLabelX,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Center,
-- adding 2 to padding to push the label away from the axis line
Position = UDim2.new(.5, 0, GRAPH_PADDING + GRAPH_SCALE, axisTextPadding),
BackgroundTransparency = 1,
}),
graph = Roact.createElement("Frame", {
Position = UDim2.new(GRAPH_PADDING, 0, GRAPH_PADDING, 0),
Size = UDim2.new(GRAPH_SCALE, 0, GRAPH_SCALE, 0),
BackgroundTransparency = 1,
[Roact.Ref] = self.graphRef,
[Roact.Event.InputChanged] = self.onGraphInputChanged,
[Roact.Event.InputEnded] = self.onGraphInputEnded,
}, elements)
})
end
return LineGraph
@@ -0,0 +1,56 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Constants)
local HOVER_LINE_COLOR = Constants.Color.HoverGreen
local LINE_WIDTH = Constants.GeneralFormatting.LineWidth
local TEXT_PADDING = Constants.Graph.TextPadding
local GRAPH_Y_INNER_PADDING = Constants.Graph.InnerPaddingY
return function(props)
local hoverLineX = props.hoverLineX
local hoverLineY = props.hoverLineY
local hoverValX = props.hoverValX
local hoverValY = props.hoverValY
local stringFormatX = props.stringFormatX
local stringFormatY = props.stringFormatY
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
}, {
hoverLine = Roact.createElement("Frame", {
Size = UDim2.new(0, LINE_WIDTH, 1, 0),
Position = UDim2.new(0, hoverLineX, 0, 0),
BackgroundColor3 = HOVER_LINE_COLOR,
BorderSizePixel = 0,
}),
HoverHorizontal = Roact.createElement("Frame", {
Size = UDim2.new(0, hoverLineX + TEXT_PADDING, 0, LINE_WIDTH),
Position = UDim2.new(0, -TEXT_PADDING, 1 - GRAPH_Y_INNER_PADDING, -hoverLineY),
BackgroundColor3 = HOVER_LINE_COLOR,
BorderSizePixel = 0,
}),
HoverTextY = Roact.createElement("TextLabel", {
Text = stringFormatY and stringFormatY(hoverValY) or hoverValY,
TextColor3 = HOVER_LINE_COLOR,
TextXAlignment = Enum.TextXAlignment.Right,
Position = UDim2.new(0, -TEXT_PADDING - 2, 1 - GRAPH_Y_INNER_PADDING, -hoverLineY),
BackgroundTransparency = 1,
}),
HoverTextX = Roact.createElement("TextLabel", {
Text = stringFormatX and stringFormatX(hoverValX) or hoverValX,
TextColor3 = HOVER_LINE_COLOR,
TextXAlignment = Enum.TextXAlignment.Center,
Position = UDim2.new(0, hoverLineX, 1, TEXT_PADDING),
BackgroundTransparency = 1,
}),
})
end
@@ -0,0 +1,308 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local DataConsumer = require(script.Parent.Parent.Components.DataConsumer)
local Actions = script.Parent.Parent.Actions
local SetActiveTab = require(Actions.SetActiveTab)
local Constants = require(script.Parent.Parent.Constants)
local MsgTypeNamesOrdered = Constants.MsgTypeNamesOrdered
local TOP_BAR_FONT_SIZE = Constants.DefaultFontSize.TopBar
local TEXT_COLOR = Constants.Color.Text
local FONT = Constants.Font.TopBar
local IMAGE_SIZE = UDim2.new(0, TOP_BAR_FONT_SIZE, 0, TOP_BAR_FONT_SIZE)
local MEM_STAT_STR_SMALL = "Client Mem:"
local memStatStrSmallWidth = TextService:GetTextSize(MEM_STAT_STR_SMALL, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
local MEM_STAT_STR = "Client Memory Usage:"
local memStatStrWidth = TextService:GetTextSize(MEM_STAT_STR, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
local AVG_PING_STR = "Avg. Ping:"
local avgPingStrWidth = TextService:GetTextSize(AVG_PING_STR, TOP_BAR_FONT_SIZE, FONT, Vector2.new(0, 0))
-- supposed to be the calculated width of the frame, but
-- doing this for now due to time constraints.
local MIN_LARGE_FORMFACTOR_WIDTH = 380
local INNER_PADDING = 6
local LiveUpdateElement = Roact.PureComponent:extend("LiveUpdateElement")
function LiveUpdateElement:didMount()
local totalMemSignal = self.props.ClientMemoryData:totalMemSignal()
self.totalMemConnector = totalMemSignal:Connect(function(totalClientMemory)
self:setState({totalClientMemory = totalClientMemory})
end)
self.avgPingConnector = self.props.ServerStatsData:avgPing():Connect(function(averagePing)
self:setState({averagePing = averagePing})
end)
self.logWarningErrorConnector = self.props.ClientLogData:errorWarningSignal():Connect(function(error, warning)
self:setState({
numErrors = error,
numWarnings = warning,
})
end)
end
function LiveUpdateElement:willUnmount()
self.totalMemConnector:Disconnect()
self.totalMemConnector = nil
self.avgPingConnector:Disconnect()
self.avgPingConnector = nil
self.logWarningErrorConnector:Disconnect()
self.logWarningErrorConnector = nil
end
function LiveUpdateElement:init()
local errorInit, warningInit = self.props.ClientLogData:getErrorWarningCount()
self.onLogWarningButton = function()
local warningFilters = {}
for _, name in pairs(MsgTypeNamesOrdered) do
warningFilters[name] = false
end
warningFilters["Warning"] = true
self.props.ClientLogData:setFilters(warningFilters)
self.props.dispatchChangeTabClientLog()
end
self.onLogErrorButton = function()
local errorFilters = {}
for _, name in pairs(MsgTypeNamesOrdered) do
errorFilters[name] = false
end
errorFilters["Error"] = true
self.props.ClientLogData:setFilters(errorFilters)
self.props.dispatchChangeTabClientLog()
end
self.ref = Roact.createRef()
self.state = {
numErrors = errorInit,
numWarnings = warningInit,
totalClientMemory = 0,
averagePing = 0,
formFactorThreshold = MIN_LARGE_FORMFACTOR_WIDTH,
}
end
function LiveUpdateElement:render()
local size = self.props.size
local position = self.props.position
local formFactor = self.props.formFactor
local numErrors = self.state.numErrors
local numWarnings = self.state.numWarnings
local clientMemoryUsage = self.state.totalClientMemory
local averagePing = self.state.averagePing
local formFactorThreshold = self.state.formFactorThreshold
local useSmallForm = false
local currMemStrWidth = memStatStrWidth.X
local alignment = Enum.HorizontalAlignment.Center
local sizeCheck
if self.ref.current then
sizeCheck = self.ref.current.AbsoluteSize.X < formFactorThreshold
end
if formFactor == Constants.FormFactor.Small or sizeCheck then
position = position + UDim2.new(0, INNER_PADDING * 2, 0, 0)
currMemStrWidth = memStatStrSmallWidth.X
useSmallForm = true
alignment = Enum.HorizontalAlignment.Left
end
local logErrorStat = string.format("%d", numErrors)
local logErrorStatVector = TextService:GetTextSize(
logErrorStat,
TOP_BAR_FONT_SIZE,
FONT,
Vector2.new(0, 0)
)
local logWarningStat = string.format("%d", numWarnings)
local logWarningStatVector = TextService:GetTextSize(
logWarningStat,
TOP_BAR_FONT_SIZE,
FONT,
Vector2.new(0, 0)
)
local memUsageString = string.format("%d MB", clientMemoryUsage)
local memUsageStringVector = TextService:GetTextSize(
memUsageString,
TOP_BAR_FONT_SIZE,
FONT,
Vector2.new(0, 0)
)
local avgPingString = string.format("%d ms", averagePing)
local avgPingStringVector = TextService:GetTextSize(avgPingString,
TOP_BAR_FONT_SIZE,
FONT,
Vector2.new(0, 0)
)
local sizeCheck
if self.ref.current then
sizeCheck = self.ref.current.AbsoluteSize.X < formFactorThreshold
end
if formFactor == Constants.FormFactor.Small or sizeCheck then
position = position + UDim2.new(0, INNER_PADDING * 2, 0, 0)
currMemStrWidth = memStatStrSmallWidth.X
useSmallForm = true
alignment = Enum.HorizontalAlignment.Left
end
local showNetworkPing = averagePing > 0
return Roact.createElement("Frame", {
Position = position,
Size = size,
BackgroundTransparency = 1,
[Roact.Ref] = self.ref,
}, {
UIListLayout = Roact.createElement("UIListLayout", {
Padding = UDim.new(0, INNER_PADDING),
HorizontalAlignment = alignment,
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
VerticalAlignment = Enum.VerticalAlignment.Center,
}),
LogErrorIcon = Roact.createElement("ImageButton", {
Image = Constants.Image.Error,
Size = IMAGE_SIZE,
BackgroundTransparency = 1,
LayoutOrder = 1,
[Roact.Event.Activated] = self.onLogErrorButton,
}),
LogErrorCount = Roact.createElement("TextButton", {
Text = logErrorStat,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Font = FONT,
Size = UDim2.new(0, logErrorStatVector.X, 1, 0),
BackgroundTransparency = 1,
LayoutOrder = 2,
[Roact.Event.Activated] = self.onLogErrorButton,
}),
ErrorWarningPad = Roact.createElement("Frame", {
BackgroundTransparency = 1,
LayoutOrder = 3,
}),
LogWarningIcon = Roact.createElement("ImageButton", {
Image = Constants.Image.Warning,
Size = IMAGE_SIZE,
BackgroundTransparency = 1,
LayoutOrder = 4,
[Roact.Event.Activated] = self.onLogWarningButton,
}),
LogWarningCount = Roact.createElement("TextButton", {
Text = logWarningStat,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Font = FONT,
Size = UDim2.new(0, logWarningStatVector.X, 1, 0),
BackgroundTransparency = 9,
LayoutOrder = 5,
[Roact.Event.Activated] = self.onLogWarningButton,
}),
WarningMemoryPad = Roact.createElement("Frame", {
BackgroundTransparency = 1,
LayoutOrder = 6,
}),
MemoryUsage = Roact.createElement("TextButton", {
Text = useSmallForm and MEM_STAT_STR_SMALL or MEM_STAT_STR,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = Constants.Color.WarningYellow,
TextXAlignment = Enum.TextXAlignment.Right,
Font = FONT,
Size = UDim2.new(0, currMemStrWidth, 1, 0),
BackgroundTransparency = 1,
LayoutOrder = 7,
[Roact.Event.Activated] = self.props.dispatchChangeTabClientMemory,
}),
MemoryUsage_MB = Roact.createElement("TextButton", {
Text = memUsageString,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Font = FONT,
Size = UDim2.new(0, memUsageStringVector.X, 1, 0),
BackgroundTransparency = 1,
LayoutOrder = 8,
[Roact.Event.Activated] = self.props.dispatchChangeTabClientMemory,
}),
MemoryPingPad = Roact.createElement("Frame", {
BackgroundTransparency = 1,
LayoutOrder = 9,
}),
AvgPing = not useSmallForm and showNetworkPing and Roact.createElement("TextButton", {
Text = AVG_PING_STR,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = Constants.Color.WarningYellow,
TextXAlignment = Enum.TextXAlignment.Right,
Font = FONT,
Size = UDim2.new(0, avgPingStrWidth.X, 1, 0),
BackgroundTransparency = 1,
LayoutOrder = 10,
[Roact.Event.Activated] = self.props.dispatchChangeTabNetworkPing,
}),
AvgPing_ms = not useSmallForm and showNetworkPing and Roact.createElement("TextButton", {
Text = avgPingString,
TextSize = TOP_BAR_FONT_SIZE,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Font = FONT,
Size = UDim2.new(0, avgPingStringVector.X, 1, 0),
BackgroundTransparency = 1,
LayoutOrder = 11,
[Roact.Event.Activated] = self.props.dispatchChangeTabNetworkPing,
})
})
end
local function mapDispatchToProps(dispatch)
return {
dispatchChangeTabClientLog = function()
dispatch(SetActiveTab("Log", true))
end,
dispatchChangeTabClientMemory = function()
dispatch(SetActiveTab("Memory", true))
end,
dispatchChangeTabNetworkPing = function()
dispatch(SetActiveTab("ServerStats", true))
end,
}
end
return RoactRodux.UNSTABLE_connect2(nil, mapDispatchToProps)(
DataConsumer(LiveUpdateElement, "ServerStatsData", "ClientMemoryData", "ClientLogData" )
)
@@ -0,0 +1,31 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local RoactRodux = require(CorePackages.RoactRodux)
local Store = require(CorePackages.Rodux).Store
local DataProvider = require(script.Parent.DataProvider)
local LiveUpdateElement = require(script.Parent.LiveUpdateElement)
it("should create and destroy without errors", function()
local store = Store.new(function()
return {
}
end)
local element = Roact.createElement(RoactRodux.StoreProvider, {
store = store,
}, {
DataProvider = Roact.createElement(DataProvider, nil, {
LiveUpdateElement = Roact.createElement(LiveUpdateElement, {
size = UDim2.new(),
position = UDim2.new(),
})
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,23 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DataConsumer = require(script.Parent.Parent.DataConsumer)
local LogOutput = require(script.Parent.LogOutput)
local ClientLog = Roact.Component:extend("ClientLog")
function ClientLog:init()
self.initClientLogData = function()
return self.props.ClientLogData:getLogData()
end
end
function ClientLog:render()
return Roact.createElement(LogOutput, {
layoutOrder = self.props.layoutOrder,
size = self.props.size,
initLogOutput = self.initClientLogData,
targetSignal = self.props.ClientLogData:Signal(),
})
end
return DataConsumer(ClientLog, "ClientLogData")
@@ -0,0 +1,16 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DataProvider = require(script.Parent.Parent.DataProvider)
local ClientLog = require(script.Parent.ClientLog)
it("should create and destroy without errors", function()
local element = Roact.createElement(DataProvider, {}, {
ClientLog = Roact.createElement(ClientLog)
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,142 @@
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local LogService = game:GetService("LogService")
local Roact = require(CorePackages.Roact)
local DataConsumer = require(script.Parent.Parent.DataConsumer)
local Constants = require(script.Parent.Parent.Parent.Constants)
local COMMANDLINE_INDENT = Constants.LogFormatting.CommandLineIndent
local COMMANDLINE_FONTSIZE = Constants.DefaultFontSize.CommandLine
local FONT = Constants.Font.MainWindow
local DevConsoleCommandLine = Roact.PureComponent:extend("DevConsoleCommandLine")
function DevConsoleCommandLine:init()
self.onFocusLost = function(rbx, enterPressed, inputThatCausedFocusLoss)
if enterPressed then
LogService:ExecuteScript(rbx.Text)
rbx:CaptureFocus()
end
end
self.ref = Roact.createRef()
end
function DevConsoleCommandLine:didMount()
if not self.onFocusConnection then
self.onFocusConnection = UserInputService.InputBegan:Connect(function(input)
if self.ref.current and self.ref.current:IsFocused() then
local rbx = self.ref.current
local serverLogData = self.props.ServerLogData
local cmdHistory = serverLogData:getCommandLineHistory()
local cmdIndex = serverLogData:getCommandLineIndex()
if input.KeyCode == Enum.KeyCode.Up then
local newIndex = cmdIndex + 1
newIndex = math.min(cmdHistory:getSize(), newIndex)
cmdIndex = newIndex
rbx.Text = cmdHistory:reverseAt(newIndex) or ""
elseif input.KeyCode == Enum.KeyCode.Down then
local newIndex = cmdIndex - 1
newIndex = math.max(0, newIndex)
cmdIndex = newIndex
rbx.Text = cmdHistory:reverseAt(newIndex) or ""
elseif input.KeyCode == Enum.KeyCode.Return then
if #rbx.Text:gsub("%s+", "") > 0 then
local prevText = cmdHistory:reverseAt(1)
if prevText ~= rbx.Text then
cmdHistory:push_back(rbx.Text)
end
end
cmdIndex = 0
elseif cmdIndex ~= 0 then
cmdIndex = 0
end
serverLogData:setCommandLineIndex(cmdIndex)
end
end)
end
-- theres an unwanted behavior here
-- if you recapture the same textbox in the onFocuslost event,
-- the "\r" character that triggerd the onFocusLost will not be consumed yet
-- and will subsequently be put into the textbox. This event is meant to fix that.
if not self.fixUnwantedReturnCapture then
self.fixUnwantedReturnCapture = UserInputService.InputEnded:Connect(function(input)
if self.ref.current and self.ref.current:IsFocused() then
if input.KeyCode == Enum.KeyCode.Return then
self.ref.current.Text = ""
end
end
end)
end
end
function DevConsoleCommandLine:willUnmount()
if self.onFocusConnection then
self.onFocusConnection:Disconnect()
self.onFocusConnection = nil
end
if self.fixUnwantedReturnCapture then
self.fixUnwantedReturnCapture:Disconnect()
self.fixUnwantedReturnCapture = nil
end
end
function DevConsoleCommandLine:render()
local height = self.props.height
local pos = self.props.pos
local initText = ""
local cmdIndex = self.props.ServerLogData:getCommandLineIndex()
if cmdIndex ~= 0 then
local cmdHistory = self.props.ServerLogData:getCommandLineHistory()
initText = cmdHistory:reverseAt(cmdIndex) or ""
end
return Roact.createElement("Frame", {
Position = pos,
Size = UDim2.new(1, 0, 0, height),
BackgroundTransparency = 0,
BackgroundColor3 = Constants.Color.TextBoxGray,
BorderColor3 = Constants.Color.BorderGray,
BorderSizePixel = 1,
}, {
Arrow = Roact.createElement("TextLabel", {
Size = UDim2.new(0, COMMANDLINE_INDENT, 1, 0),
BackgroundTransparency = 1,
TextSize = COMMANDLINE_FONTSIZE,
Font = FONT,
Text = "> ",
TextColor3 = Constants.Color.Text,
TextXAlignment = Enum.TextXAlignment.Right,
}),
TextBox = Roact.createElement("TextBox", {
Position = UDim2.new(0, COMMANDLINE_INDENT, 0, 0),
Size = UDim2.new(1, -COMMANDLINE_INDENT, 0, height),
BackgroundTransparency = 1,
ShowNativeInput = true,
ClearTextOnFocus = false,
TextColor3 = Constants.Color.Text,
TextXAlignment = 0,
TextSize = COMMANDLINE_FONTSIZE,
Text = initText,
Font = FONT,
PlaceholderText = "command line",
[Roact.Ref] = self.ref,
[Roact.Event.FocusLost] = self.onFocusLost,
})
})
end
return DataConsumer(DevConsoleCommandLine, "ServerLogData")
@@ -0,0 +1,16 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local DataProvider = require(script.Parent.Parent.DataProvider)
local DevConsoleCommandLine = require(script.Parent.DevConsoleCommandLine)
it("should create and destroy without errors", function()
local element = Roact.createElement(DataProvider, {}, {
CmdLine = Roact.createElement(DevConsoleCommandLine),
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,335 @@
local LogService = game:GetService("LogService")
local TextService = game:GetService("TextService")
local Constants = require(script.Parent.Parent.Parent.Constants)
local FONT_SIZE = Constants.DefaultFontSize.MainWindow
local FONT = Constants.Font.Log
local MAX_STRING_SIZE = Constants.LogFormatting.MaxStringSize
local MESSAGE_TO_TYPENAME = Constants.EnumToMsgTypeName
local CircularBuffer = require(script.Parent.Parent.Parent.CircularBuffer)
local Signal = require(script.Parent.Parent.Parent.Signal)
-- 500 is max kept in history in C++ as of 7/2/2018
local MAX_LOG_SIZE = tonumber(settings():GetFVariable("NewDevConsoleMaxLogCount"))
local WARNING_TO_FILTER = {"ClassDescriptor failed to learn", "EventDescriptor failed to learn", "Type failed to learn"}
local MAX_HISTORY = 100
local convertTimeStamp = require(script.Parent.Parent.Parent.Util.convertTimeStamp)
local LogData = {}
LogData.__index = LogData
local function messageEntry(msg, timeAsStr, type)
local fmtMessage
local charCount = #msg
if charCount < MAX_STRING_SIZE then
fmtMessage = string.format("%s -- %s", timeAsStr, msg)
else
fmtMessage = string.format("%s -- %s", timeAsStr, string.sub(msg, 1, MAX_STRING_SIZE))
end
local dims = TextService:GetTextSize(fmtMessage, FONT_SIZE, FONT, Vector2.new())
return {
Message = fmtMessage,
CharCount = charCount,
Type = type,
Dims = dims,
}
end
-- MOST if not all of this code is copied from the
-- Filter "ClassDescriptor failed to learn" errors
local function ignoreWarningMessageOnAdd(message)
if message.Type ~= Enum.MessageType.MessageWarning.Value then
return false
end
local found = false
for _, filterString in ipairs(WARNING_TO_FILTER) do
if string.find(message.Message, filterString) ~= nil then
found = true
break
end
end
return found
end
-- if a message if filtered that means we need to put it into the filtered messages
-- this is defined as the messages that we are searching for when the search
-- feature is being used
local function isMessageFiltered(message, filterTypes, filterTerm)
-- if any types are flagged on, then we need to check against it
if #filterTerm == 0 and not next(filterTypes) then
return false
end
if next(filterTypes) then
if not filterTypes[MESSAGE_TO_TYPENAME[message.Type]] then
return false
end
end
if #filterTerm > 0 then
if string.find(message.Message:lower(), filterTerm:lower()) == nil then
return false
end
end
return true
end
local function validActiveFilters(filterTypes)
local validFilters = false
for _, filterActive in pairs(filterTypes) do
validFilters = validFilters or filterActive
end
return validFilters
end
local function filterMessages(buffer, msgIter, filterTypes, filterTerm)
buffer:reset()
local validFilters = validActiveFilters(filterTypes)
if #filterTerm == 0 and not validFilters then
return
end
local counter = 0
local msg = msgIter:next()
while msg do
if isMessageFiltered(msg, filterTypes, filterTerm) then
counter = counter + 1
buffer:push_back(msg)
end
msg = msgIter:next()
end
if counter == 0 then
if #filterTerm > 0 then
local errorMsg = messageEntry(string.format ("\"%s\" was not found", filterTerm), "", 0)
buffer:push_back(errorMsg)
else
local errorMsg = messageEntry("No Messages were found", "", 0)
buffer:push_back(errorMsg)
end
end
end
function LogData:checkErrorWarningCounter(msgType)
if msgType == Enum.MessageType.MessageWarning.Value then
self._warningCount = self._warningCount + 1
self._errorWarningSignal:Fire(self._errorCount, self._warningCount)
elseif msgType == Enum.MessageType.MessageError.Value then
self._errorCount = self._errorCount + 1
self._errorWarningSignal:Fire(self._errorCount, self._warningCount)
end
end
function LogData.new(isClient)
local self = {}
setmetatable(self, LogData)
self._initialized = false
self._isClient = isClient
self._logData = CircularBuffer.new(MAX_LOG_SIZE)
self._logDataSearched = CircularBuffer.new(MAX_LOG_SIZE)
self._searchTerm = ""
self._commandLineHistory = CircularBuffer.new(MAX_HISTORY)
self._commandLineIndex = 0
self._filters = {}
for _, v in pairs(MESSAGE_TO_TYPENAME) do
self._filters[v] = true
end
self._errorCount = isClient and 0
self._warningCount = isClient and 0
self._logDataUpdate = Signal.new()
self._errorWarningSignal = isClient and Signal.new()
self._filterUpdated = Signal.new()
return self
end
function LogData:Signal()
return self._logDataUpdate
end
function LogData:errorWarningSignal()
return self._errorWarningSignal
end
function LogData:filterUpdatedSignal()
return self._filterUpdated
end
function LogData:setSearchTerm(targetSearchTerm)
if self._searchTerm ~= targetSearchTerm then
self._searchTerm = targetSearchTerm
if self._searchTerm == "" then
self._logDataSearched:reset()
self._logDataUpdate:Fire(self._logData)
else
filterMessages(
self._logDataSearched,
self._logData:iterator(),
self._filters,
self._searchTerm
)
self._logDataUpdate:Fire(self._logDataSearched)
end
end
end
function LogData:getSearchTerm()
return self._searchTerm
end
function LogData:getCommandLineHistory()
return self._commandLineHistory
end
function LogData:getCommandLineIndex()
return self._commandLineIndex
end
function LogData:setCommandLineIndex(index)
self._commandLineIndex = index
end
function LogData:getFilters()
return self._filters
end
function LogData:setFilter(name, newState)
self._filters[name] = newState
if not validActiveFilters(self._filters) then
self._logDataSearched:reset()
self._logDataUpdate:Fire(self._logData)
return
end
filterMessages(
self._logDataSearched,
self._logData:iterator(),
self._filters,
self._searchTerm
)
self._logDataUpdate:Fire(self._logDataSearched)
self._filterUpdated:Fire()
end
function LogData:setFilters(filters)
self._filters = filters
if not validActiveFilters(filters) then
self._logDataSearched:reset()
self._logDataUpdate:Fire(self._logData)
return
end
filterMessages(
self._logDataSearched,
self._logData:iterator(),
self._filters,
self._searchTerm
)
self._logDataUpdate:Fire(self._logDataSearched)
self._filterUpdated:Fire()
end
function LogData:getLogData()
if #self._logDataSearched:getData() > 0 then
return self._logDataSearched
end
return self._logData
end
function LogData:getErrorWarningCount()
return self._errorCount, self._warningCount
end
function LogData:start()
if self._isClient then
if not self._initialized then
self._initialized = true
local Messages = {}
if #Messages == 0 then
local history = LogService:GetLogHistory()
for _, msg in ipairs(history) do
local message = messageEntry(
msg.message or "[DevConsole Error 1]",
convertTimeStamp(msg.timestamp),
msg.messageType.Value
)
if not ignoreWarningMessageOnAdd(message) then
self:checkErrorWarningCounter(msg.messageType.Value)
self._logData:push_back(message)
end
end
end
end
self._connection = LogService.MessageOut:connect(function(text, messageType)
local message = messageEntry(
text or "[DevConsole Error 2]",
convertTimeStamp(os.time()),
messageType.Value
)
if not ignoreWarningMessageOnAdd(message) then
self:checkErrorWarningCounter(messageType.Value)
self._logData:push_back(message)
if #self._logDataSearched:getData() > 0 then
if isMessageFiltered(message, self._filters, self._searchTerm) then
self._logDataSearched:push_back(message)
self._logDataUpdate:Fire(self._logDataSearched)
end
else
self._logDataUpdate:Fire(self._logData)
end
end
end)
else
self._connection = LogService.ServerMessageOut:connect(function(text, messageType, timestamp)
local message = messageEntry(
text or "[DevConsole Error 3]",
convertTimeStamp(timestamp),
messageType.Value
)
if not ignoreWarningMessageOnAdd(message) then
self._logData:push_back(message)
if #self._logDataSearched:getData() > 0 then
if isMessageFiltered(message, self._filters, self._searchTerm) then
self._logDataSearched:push_back(message)
self._logDataUpdate:Fire(self._logDataSearched)
end
else
self._logDataUpdate:Fire(self._logData)
end
end
end)
LogService:RequestServerOutput()
end
end
function LogData:stop()
self._initialized = false
if self._connection then
self._connection:Disconnect()
end
end
return LogData
@@ -0,0 +1,24 @@
return function()
local LogData = require(script.Parent.LogData)
it("should initialize either client or server", function()
local clientLogData = LogData.new(true)
local serverLogData = LogData.new(false)
expect(clientLogData).to.be.ok()
expect(serverLogData).to.be.ok()
end)
it("should get and set the filters", function()
local clientLogData = LogData.new(true)
local key = "Output"
local value = false
local filters = clientLogData:getFilters()
expect(filters[key]).to.equal(true)
clientLogData:setFilter(key, value)
filters = clientLogData:getFilters()
expect(filters[key]).to.equal(value)
end)
end
@@ -0,0 +1,208 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local Constants = require(script.Parent.Parent.Parent.Constants)
local FONT_SIZE = Constants.DefaultFontSize.MainWindow
local FONT = Constants.Font.Log
local ICON_PADDING = Constants.LogFormatting.IconHeight
local FRAME_HEIGHT = Constants.LogFormatting.TextFrameHeight
local LINE_PADDING = Constants.LogFormatting.TextFramePadding
local MAX_STRING_SIZE = Constants.LogFormatting.MaxStringSize
local MAX_STR_MSG = " -- Could not display entire %d character message because message exceeds max displayable length of %d"
local LogOutput = Roact.Component:extend("LogOutput")
function LogOutput:init(props)
local initLogOutput = props.initLogOutput and props.initLogOutput()
self.onCanvasChange = function()
local canvasPos = self.ref.current.CanvasPosition
local absSize = self.ref.current.AbsoluteSize
if self.state.canvasPos ~= canvasPos or
self.state.absSize ~= absSize then
self:setState({
canvasPos = canvasPos,
absSize = absSize,
})
end
end
self.ref = Roact.createRef()
self.state = {
logData = initLogOutput,
absSize = Vector2.new(),
canvasPos = UDim2.new(),
wordWrap = true,
}
end
function LogOutput:willUpdate(nextProps, nextState)
self._canvasSignal:Disconnect()
self._absSizeSignal:Disconnect()
end
function LogOutput:didUpdate()
self._canvasSignal = self.ref.current:GetPropertyChangedSignal("CanvasPosition"):Connect(self.onCanvasChange)
self._absSizeSignal = self.ref.current:GetPropertyChangedSignal("AbsoluteSize"):Connect(self.onCanvasChange)
end
function LogOutput:didMount()
self.logConnector = self.props.targetSignal:Connect(function(data)
self:setState({
logData = data
})
end)
self._canvasSignal = self.ref.current:GetPropertyChangedSignal("CanvasPosition"):Connect(self.onCanvasChange)
self._absSizeSignal = self.ref.current:GetPropertyChangedSignal("AbsoluteSize"):Connect(self.onCanvasChange)
--[[
in some cases, the absolute size is not valid at this point. But in the
case that it is, we want to update the absolute size here since the
absolute size was changed prior to the absSizeSignal being set up
--]]
local absSize = self.ref.current.AbsoluteSize
if absSize.Magnitude > 0 then
self:setState({
absSize = self.ref.current.AbsoluteSize,
})
end
end
function LogOutput:willUnmount()
self.logConnector:Disconnect()
self.logConnector = nil
end
function LogOutput:render()
local layoutOrder = self.props.layoutOrder
local size = self.props.size
local logData = self.state.logData
local absSize = self.state.absSize
local canvasPos = self.state.canvasPos
local wordWrap = self.state.wordWrap
if self.ref.current then
canvasPos = self.ref.current.CanvasPosition
end
local elements = {}
local messageCount = 1
local scrollingFrameHeight = 0
if self.ref.current and logData then
-- FRAME_HEIGHT is used to offset the text for the icon
local frameWidth = absSize.X - FRAME_HEIGHT
local paddingHeight = -1
local usedFrameSpace = 0
local msgIter = logData:iterator()
local message = msgIter:next()
while message do
local fmtMessage = message.Message
local charCount = message.CharCount
local msgDimsY = message.Dims.Y
if wordWrap then
msgDimsY = message.Dims.Y * math.ceil(message.Dims.X / frameWidth)
end
messageCount = messageCount + 1
if scrollingFrameHeight + msgDimsY >= canvasPos.Y then
if usedFrameSpace < absSize.Y then
local color = Constants.Color.Text
local image = ""
if message.Type == Enum.MessageType.MessageOutput.Value then
color = Constants.Color.Text
elseif message.Type == Enum.MessageType.MessageInfo.Value then
color = Constants.Color.HighlightBlue
image = Constants.Image.Info
elseif message.Type == Enum.MessageType.MessageWarning.Value then
color = Constants.Color.WarningYellow
image = Constants.Image.Warning
elseif message.Type == Enum.MessageType.MessageError.Value then
color = Constants.Color.ErrorRed
image = Constants.Image.Errors
end
elements[messageCount] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, msgDimsY),
BackgroundTransparency = 1,
LayoutOrder = messageCount,
}, {
image = Roact.createElement("ImageLabel", {
Image = image,
Size = UDim2.new(0, ICON_PADDING , 0, ICON_PADDING),
Position = UDim2.new(0, ICON_PADDING / 4, .5, -ICON_PADDING / 2),
BackgroundTransparency = 1,
}),
msg = Roact.createElement("TextLabel", {
Text = fmtMessage,
TextColor3 = color,
TextSize = FONT_SIZE,
Font = FONT,
TextXAlignment = Enum.TextXAlignment.Left,
TextWrapped = wordWrap,
Size = UDim2.new(1, 0, 0, msgDimsY),
Position = UDim2.new(0, FRAME_HEIGHT, 0, 0),
BackgroundTransparency = 1,
})
})
end
if paddingHeight < 0 then
paddingHeight = scrollingFrameHeight
else
usedFrameSpace = usedFrameSpace + msgDimsY + LINE_PADDING
end
end
scrollingFrameHeight = scrollingFrameHeight + msgDimsY + LINE_PADDING
if charCount < MAX_STRING_SIZE then
message = msgIter:next()
else
local maxStrMsg = string.format(MAX_STR_MSG, charCount, MAX_STRING_SIZE)
message = {
Message = maxStrMsg,
CharCount = #maxStrMsg,
Type = message.Type,
Dims = TextService:GetTextSize(maxStrMsg, FONT_SIZE, FONT, Vector2.new())
}
end
end
elements["UIListLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, LINE_PADDING),
})
elements["WindowingPadding"] = Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 0, paddingHeight),
BackgroundTransparency = 1,
LayoutOrder = 1,
})
end
return Roact.createElement("ScrollingFrame", {
Size = size,
BackgroundTransparency = 1,
VerticalScrollBarInset = 1,
ScrollBarThickness = 6,
CanvasSize = UDim2.new(1, 0, 0, scrollingFrameHeight),
LayoutOrder = layoutOrder,
[Roact.Ref] = self.ref,
}, elements)
end
return LogOutput
@@ -0,0 +1,16 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Signal = require(script.Parent.Parent.Parent.Signal)
local LogOutput = require(script.Parent.LogOutput)
it("should create and destroy without errors", function()
local element = Roact.createElement(LogOutput, {
targetSignal = Signal.new()
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end

Some files were not shown because too many files have changed in this diff Show More