add gs
This commit is contained in:
@@ -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
+239
@@ -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
|
||||
+269
@@ -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()
|
||||
+314
@@ -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
|
||||
+278
@@ -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()
|
||||
+226
@@ -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()
|
||||
+102
@@ -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
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ActionBindingsUpdateSearchFilter", function(searchTerm)
|
||||
return {
|
||||
searchTerm = searchTerm
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ChangeDevConsoleSize", function(newSize)
|
||||
return {
|
||||
newSize = newSize
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientLogUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ClientScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("DataStoresUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerJobsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerLogUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerMemoryUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerNetworkUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("ServerScriptsUpdateSearchFilter", function(searchTerm, filterTypes)
|
||||
return {
|
||||
searchTerm = searchTerm,
|
||||
filterTypes = filterTypes
|
||||
}
|
||||
end)
|
||||
+8
@@ -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)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetDevConsoleMinimized", function(minimize)
|
||||
return {
|
||||
isMinimized = minimize
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local Action = require(script.Parent.Parent.Action)
|
||||
|
||||
return Action("SetDevConsolePosition", function(pos)
|
||||
return {
|
||||
position = pos
|
||||
}
|
||||
end)
|
||||
+7
@@ -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)
|
||||
+7
@@ -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
|
||||
+144
@@ -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
|
||||
+350
@@ -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")
|
||||
+15
@@ -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
|
||||
+151
@@ -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
|
||||
+105
@@ -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)
|
||||
+36
@@ -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
|
||||
+56
@@ -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
|
||||
+32
@@ -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
|
||||
+18
@@ -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
|
||||
+161
@@ -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
|
||||
+15
@@ -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
|
||||
+52
@@ -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
|
||||
+15
@@ -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
|
||||
+101
@@ -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
|
||||
+13
@@ -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
|
||||
+41
@@ -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
|
||||
+49
@@ -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
|
||||
+239
@@ -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")
|
||||
+16
@@ -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
|
||||
+108
@@ -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
|
||||
+105
@@ -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)
|
||||
+36
@@ -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
|
||||
+184
@@ -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)
|
||||
+32
@@ -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
|
||||
+317
@@ -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)
|
||||
+51
@@ -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
|
||||
+154
@@ -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
|
||||
+14
@@ -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
|
||||
+153
@@ -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
|
||||
+14
@@ -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
|
||||
+34
@@ -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
|
||||
+305
@@ -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
|
||||
+56
@@ -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
|
||||
+308
@@ -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" )
|
||||
)
|
||||
+31
@@ -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
|
||||
+23
@@ -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")
|
||||
+16
@@ -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
|
||||
+142
@@ -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")
|
||||
+16
@@ -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
|
||||
+335
@@ -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
|
||||
+24
@@ -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
|
||||
+208
@@ -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
|
||||
+16
@@ -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
Reference in New Issue
Block a user