add
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
--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.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/Menu/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/Menu/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)
|
||||
|
||||
|
||||
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
|
||||
|
||||
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/Menu/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 in_backpack = tool.Parent == player.Backpack
|
||||
humanoid:UnequipTools()
|
||||
if in_backpack 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()
|
||||
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.new(0, -3.5, 5) * CFrame.Angles(math.rad(20), 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
|
||||
if self.transparency == 1 then
|
||||
local headForwardCF = Panel3D.GetHeadLookXZ()
|
||||
local panelOriginCF = CFrame.new(userHeadCF.p) * headForwardCF
|
||||
self.localCF = panelOriginCF * panelLocalCF
|
||||
end
|
||||
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
|
||||
|
||||
return BackpackScript
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,491 @@
|
||||
--Panel3D: 3D GUI panels for VR
|
||||
--written by 0xBAADF00D
|
||||
local PIXELS_PER_STUD = 64
|
||||
local SETTINGS_DISTANCE = 3.5
|
||||
|
||||
local CURSOR_HIDE_TIME = 2
|
||||
local CURSOR_FADE_TIME = 0.125
|
||||
|
||||
local CoreGui = game:GetService('CoreGui')
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
|
||||
local Panel3D = {}
|
||||
|
||||
Panel3D.Panels = {
|
||||
Lower = 1,
|
||||
Hamburger = 2,
|
||||
Settings = 3,
|
||||
Keyboard = 4,
|
||||
Chat = 5
|
||||
}
|
||||
|
||||
Panel3D.Orientation = {
|
||||
Horizontal = 1,
|
||||
Fixed = 2
|
||||
}
|
||||
|
||||
Panel3D.Visibility = {
|
||||
BelowAngleThreshold = 1,
|
||||
Modal = 2,
|
||||
Normal = 3
|
||||
}
|
||||
|
||||
local panelDefaultVectors = {
|
||||
[Panel3D.Panels.Lower] = CFrame.Angles(math.rad(-45), 0, 0):vectorToWorldSpace(Vector3.new(0, 0, -5)),
|
||||
[Panel3D.Panels.Hamburger] = CFrame.Angles(math.rad(-55), 0, 0):vectorToWorldSpace(Vector3.new(0, 0, -5)),
|
||||
[Panel3D.Panels.Settings] = Vector3.new(0, 0, -SETTINGS_DISTANCE),
|
||||
[Panel3D.Panels.Keyboard] = CFrame.Angles(math.rad(-22.5), 0, 0):vectorToWorldSpace(Vector3.new(0, 0, -5)),
|
||||
[Panel3D.Panels.Chat] = CFrame.Angles(math.rad(5), 0, 0):vectorToWorldSpace(Vector3.new(0, 0, -5))
|
||||
}
|
||||
local DEFAULT_PANEL_LOCK_THRESHOLD = math.rad(-25)
|
||||
local panelTransparencyBias = { --tuned values; raise the opacity value to this power
|
||||
[Panel3D.Panels.Lower] = 6.5,
|
||||
[Panel3D.Panels.Hamburger] = 8,
|
||||
[Panel3D.Panels.Settings] = 0,
|
||||
[Panel3D.Panels.Keyboard] = 1.5,
|
||||
[Panel3D.Panels.Chat] = 1.5
|
||||
}
|
||||
local panels = {}
|
||||
|
||||
local headScale = 1
|
||||
|
||||
local renderStepName = "Panel3D"
|
||||
|
||||
local cursor = Instance.new("ImageLabel")
|
||||
cursor.Image = "rbxasset://textures/Cursors/Gamepad/Pointer.png"
|
||||
cursor.Size = UDim2.new(0, 8, 0, 8)
|
||||
cursor.BackgroundTransparency = 1
|
||||
cursor.ZIndex = 10
|
||||
|
||||
local resetOrientationFlag = false
|
||||
local currentModalPanel = nil
|
||||
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local cursorHidden = false
|
||||
local hasTool = false
|
||||
local lastMouseMove = tick()
|
||||
|
||||
local function OnCharacterAdded(character)
|
||||
hasTool = false
|
||||
for i, v in ipairs(character:GetChildren()) do
|
||||
if v:IsA("Tool") then
|
||||
hasTool = true
|
||||
end
|
||||
end
|
||||
character.ChildAdded:connect(function(child)
|
||||
if child:IsA("Tool") then
|
||||
hasTool = true
|
||||
lastMouseMove = tick() --kick the mouse when a tool is equipped
|
||||
end
|
||||
end)
|
||||
character.ChildRemoved:connect(function(child)
|
||||
if child:IsA("Tool") then
|
||||
hasTool = false
|
||||
end
|
||||
end)
|
||||
end
|
||||
spawn(function()
|
||||
while not game.Players.LocalPlayer do wait() end
|
||||
game.Players.LocalPlayer.CharacterAdded:connect(OnCharacterAdded)
|
||||
if game.Players.LocalPlayer.Character then OnCharacterAdded(game.Players.LocalPlayer.Character) end
|
||||
end)
|
||||
local function autoHideCursor(hide)
|
||||
if not UserInputService.VREnabled then
|
||||
cursorHidden = false
|
||||
return
|
||||
end
|
||||
if hide then
|
||||
--don't hide if there's a tool in the character
|
||||
local character = game.Players.LocalPlayer.Character
|
||||
if character and hasTool then
|
||||
return
|
||||
end
|
||||
cursorHidden = true
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
|
||||
else
|
||||
cursorHidden = false
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None
|
||||
end
|
||||
end
|
||||
|
||||
local function isCursorVisible()
|
||||
--if ForceShow, the cursor is definitely visible at all times
|
||||
if UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.ForceShow then
|
||||
return true
|
||||
end
|
||||
--if ForceHide, the cursor is definitely NOT visible
|
||||
if UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.ForceHide then
|
||||
return false
|
||||
end
|
||||
--Otherwise, we need to check if the developer set MouseIconEnabled=false
|
||||
if UserInputService.MouseIconEnabled and UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.None then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
UserInputService.InputChanged:connect(function(inputObj, processed)
|
||||
if inputObj.UserInputType == Enum.UserInputType.MouseMovement then
|
||||
lastMouseMove = tick()
|
||||
autoHideCursor(false)
|
||||
end
|
||||
end)
|
||||
game:GetService("RunService").Heartbeat:connect(function()
|
||||
if isCursorVisible() then
|
||||
cursorHidden = false
|
||||
end
|
||||
if lastMouseMove + CURSOR_HIDE_TIME < tick() and not GuiService.MenuIsOpen and not cursorHidden then
|
||||
autoHideCursor(true)
|
||||
end
|
||||
end)
|
||||
|
||||
local function createPanel()
|
||||
local panelPart = Instance.new("Part")
|
||||
panelPart.Transparency = 1
|
||||
panelPart.CanCollide = false
|
||||
panelPart.Anchored = true
|
||||
panelPart.Archivable = false
|
||||
panelPart.Size = Vector3.new(1.5, 1.5, 1)
|
||||
panelPart.Parent = workspace.CurrentCamera
|
||||
local panelGUI = Instance.new("SurfaceGui", CoreGui)
|
||||
panelGUI.Name = "GUI"
|
||||
panelGUI.Adornee = panelPart
|
||||
panelGUI.ToolPunchThroughDistance = 1000
|
||||
panelGUI.Active = true
|
||||
pcall(function() --todo: remove this when api is live
|
||||
panelGUI.AlwaysOnTop = true
|
||||
end)
|
||||
return panelPart, panelGUI
|
||||
end
|
||||
|
||||
function Panel3D.Get(panelId)
|
||||
local panel = panels[panelId]
|
||||
if not panel then
|
||||
local panelName
|
||||
for name, value in pairs(Panel3D.Panels) do
|
||||
if value == panelId then
|
||||
panelName = name
|
||||
break
|
||||
end
|
||||
end
|
||||
if not panelName then
|
||||
error("Tried to request an invalid 3D panel")
|
||||
end
|
||||
|
||||
local part, gui = createPanel()
|
||||
|
||||
panel = {}
|
||||
panel.part = part
|
||||
panel.part.Name = "GUI"--("%sPanel3D"):format(panelName)
|
||||
panel.gui = gui
|
||||
panel.gui.Name = ("%sPanelGUI"):format(panelName)
|
||||
|
||||
panel.vector = panelDefaultVectors[panelId]
|
||||
panel.horizontalVector = Vector3.new(panel.vector.x, 0, panel.vector.z).unit
|
||||
panel.pitchAngle = math.asin(panel.vector.unit.y)
|
||||
panel.verticalRange = math.rad(5)
|
||||
panel.horizontalRange = math.rad(5)
|
||||
|
||||
panel.transparencyCallbacks = {}
|
||||
|
||||
panel.OnMouseEnter = nil
|
||||
panel.OnMouseLeave = nil
|
||||
|
||||
panel.pixelScale = 1
|
||||
|
||||
panel.visible = true
|
||||
panel.visibilityBehavior = Panel3D.Visibility.BelowAngleThreshold
|
||||
|
||||
panel.orientationMode = Panel3D.Orientation.Horizontal
|
||||
panel.orientation = nil
|
||||
|
||||
panel.cursorEnabled = true
|
||||
|
||||
panel.width = 1
|
||||
panel.height = 1
|
||||
|
||||
function panel:AddTransparencyCallback(callback)
|
||||
table.insert(panel.transparencyCallbacks, callback)
|
||||
end
|
||||
|
||||
function panel:Resize(width, height, pixelsPerStud)
|
||||
panel.width = width
|
||||
panel.height = height
|
||||
|
||||
pixelsPerStud = pixelsPerStud or PIXELS_PER_STUD
|
||||
panel.pixelScale = pixelsPerStud / PIXELS_PER_STUD
|
||||
panel.part.Size = Vector3.new(panel.width * headScale, panel.height * headScale, 1)
|
||||
panel.gui.CanvasSize = Vector2.new(pixelsPerStud * panel.width, pixelsPerStud * panel.height)
|
||||
|
||||
local distance = panel.vector.magnitude
|
||||
panel.verticalRange = math.atan(panel.part.Size.Y / (2 * distance)) * 2
|
||||
panel.horizontalRange = math.atan(panel.part.Size.X / (2 * distance)) * 2
|
||||
end
|
||||
|
||||
function panel:ResizePixels(width, height)
|
||||
local widthStuds = width / PIXELS_PER_STUD
|
||||
local heightStuds = height / PIXELS_PER_STUD
|
||||
panel:Resize(widthStuds, heightStuds)
|
||||
end
|
||||
|
||||
function panel:SetModal()
|
||||
panel.visible = false
|
||||
panel.visibilityBehavior = Panel3D.Visibility.Modal
|
||||
|
||||
if panel.visible then
|
||||
currentModalPanel = panel
|
||||
if panel.orientationMode == Panel3D.Orientation.Fixed then
|
||||
local userHeadCFrame = UserInputService:GetUserCFrame(Enum.UserCFrame.Head)
|
||||
panel.orientation = userHeadCFrame * CFrame.new(0, 0, -panel.vector.magnitude) * CFrame.Angles(0, math.pi, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function panel:SetVisible(visible)
|
||||
panel.visible = visible
|
||||
|
||||
if visible and panel.visibilityBehavior == Panel3D.Visibility.Modal then
|
||||
currentModalPanel = panel
|
||||
if panel.orientationMode == Panel3D.Orientation.Fixed then
|
||||
local userHeadCFrame = UserInputService:GetUserCFrame(Enum.UserCFrame.Head)
|
||||
panel.orientation = userHeadCFrame * CFrame.new(0, 0, -panel.vector.magnitude) * CFrame.Angles(0, math.pi, 0)
|
||||
end
|
||||
else
|
||||
if currentModalPanel == panel then
|
||||
currentModalPanel = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
panels[panelId] = panel
|
||||
end
|
||||
return panel
|
||||
end
|
||||
|
||||
function Panel3D.ResetOrientation()
|
||||
resetOrientationFlag = true
|
||||
end
|
||||
|
||||
function Panel3D.GetGUI(panel)
|
||||
local panelGUI = panelGUIs[panel]
|
||||
if not panelGUI then
|
||||
local part = Panel3D.GetPart(panel)
|
||||
panelGUI = panelGUIs[panel]
|
||||
end
|
||||
return panelGUI
|
||||
end
|
||||
|
||||
local zeroVector = Vector3.new(0, 0, 0)
|
||||
local headXZ = CFrame.new()
|
||||
local headOffset = Vector3.new()
|
||||
local currentHoverPanel = nil
|
||||
local savedMouseBehavior = Enum.MouseBehavior.Default
|
||||
function Panel3D.OnRenderStep()
|
||||
if not UserInputService.VREnabled then
|
||||
return
|
||||
end
|
||||
local cameraCFrame = workspace.CurrentCamera.CFrame
|
||||
local cameraRenderCFrame = workspace.CurrentCamera:GetRenderCFrame()
|
||||
local userHeadCFrame = UserInputService:GetUserCFrame(Enum.UserCFrame.Head)
|
||||
|
||||
local userHeadLook = userHeadCFrame.lookVector
|
||||
local userHeadHorizontalVector = Vector3.new(userHeadLook.X, 0, userHeadLook.Z).unit
|
||||
local userHeadPitch = math.asin(userHeadLook.Y)
|
||||
|
||||
local panelLockThreshold = DEFAULT_PANEL_LOCK_THRESHOLD
|
||||
|
||||
for panelId, panel in pairs(panels) do
|
||||
if panel.pitchLockThreshold and panel.visible then
|
||||
panelLockThreshold = math.max(panelLockThreshold, panel.pitchLockThreshold)
|
||||
end
|
||||
end
|
||||
|
||||
local isAboveThreshold = false
|
||||
if userHeadPitch > panelLockThreshold or resetOrientationFlag then
|
||||
isAboveThreshold = true
|
||||
headXZ = CFrame.new(zeroVector, userHeadHorizontalVector)
|
||||
headOffset = userHeadCFrame.p
|
||||
resetOrientationFlag = false
|
||||
end
|
||||
|
||||
local panelsOrigin = cameraCFrame * CFrame.new(headOffset) * headXZ
|
||||
for panelId, panel in pairs(panels) do
|
||||
if panel.visibilityBehavior == Panel3D.Visibility.BelowAngleThreshold then
|
||||
panel.visible = not (isAboveThreshold or currentModalPanel)
|
||||
end
|
||||
|
||||
if not panel.visible then
|
||||
panel.part.Parent = nil
|
||||
panel.gui.Adornee = nil
|
||||
else
|
||||
panel.part.Parent = workspace.CurrentCamera --TODO: move to new 3D gui space
|
||||
panel.gui.Adornee = panel.part
|
||||
|
||||
local panelCFrame;
|
||||
if panel.orientationMode == Panel3D.Orientation.Fixed and panel.orientation then
|
||||
local pos = panel.orientation.p * headScale
|
||||
panel.part.CFrame = workspace.CurrentCamera.CFrame * ((panel.orientation - panel.orientation.p) + pos)
|
||||
panelCFrame = panel.part.CFrame
|
||||
else
|
||||
local panelPosition = panelsOrigin:pointToWorldSpace(panel.vector * headScale)
|
||||
panelCFrame = CFrame.new(panelPosition, panelsOrigin.p)
|
||||
panel.part.CFrame = panelCFrame
|
||||
end
|
||||
|
||||
local toPanel = (panelCFrame.p - cameraRenderCFrame.p).unit
|
||||
|
||||
local transparency = panel.visible and 1 - (math.max(0, cameraRenderCFrame.lookVector:Dot(toPanel)) ^ panelTransparencyBias[panelId]) or 1
|
||||
for _, callback in pairs(panel.transparencyCallbacks) do
|
||||
callback(transparency)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--Render a cursor overlaid onto the panels
|
||||
local cframe = cameraRenderCFrame
|
||||
local ray = Ray.new(cframe.p, cframe.lookVector * 999)
|
||||
local ignoreList = { game.Players.LocalPlayer.Character }
|
||||
local part, endpoint = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
|
||||
local hitPanel = nil
|
||||
local hitPanelId = nil
|
||||
for panelId, panel in pairs(panels) do
|
||||
if part == panel.part then
|
||||
hitPanel = panel
|
||||
hitPanelId = panelId
|
||||
end
|
||||
end
|
||||
if hitPanel then
|
||||
if not currentHoverPanel then
|
||||
savedMouseBehavior = UserInputService.MouseBehavior
|
||||
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
|
||||
else
|
||||
if currentHoverPanel ~= hitPanel then
|
||||
if currentHoverPanel.OnMouseLeave then
|
||||
currentHoverPanel:OnMouseLeave()
|
||||
end
|
||||
end
|
||||
end
|
||||
if hitPanel.OnMouseEnter and currentHoverPanel ~= hitPanel then
|
||||
hitPanel:OnMouseEnter()
|
||||
end
|
||||
|
||||
currentHoverPanel = hitPanel
|
||||
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
|
||||
if hitPanel.cursorEnabled then
|
||||
cursor.Parent = hitPanel.gui
|
||||
else
|
||||
cursor.Parent = nil
|
||||
end
|
||||
|
||||
local localEndpoint = part:GetRenderCFrame():pointToObjectSpace(endpoint)
|
||||
local x = (localEndpoint.X / part.Size.X) + 0.5
|
||||
local y = (localEndpoint.Y / part.Size.Y) + 0.5
|
||||
x = 1 - x
|
||||
y = 1 - y
|
||||
cursor.Size = UDim2.new(0, 8 * hitPanel.pixelScale, 0, 8 * hitPanel.pixelScale)
|
||||
cursor.Position = UDim2.new(x, -cursor.AbsoluteSize.x * 0.5, y, -cursor.AbsoluteSize.y * 0.5)
|
||||
|
||||
if hitPanel.visible and not modalPanelIsOpen then
|
||||
hitPanel.part.Parent = workspace.CurrentCamera
|
||||
for _, callback in pairs(hitPanel.transparencyCallbacks) do
|
||||
callback(0)
|
||||
end
|
||||
end
|
||||
else
|
||||
if currentHoverPanel then
|
||||
UserInputService.MouseBehavior = savedMouseBehavior
|
||||
|
||||
if currentHoverPanel.OnMouseLeave then
|
||||
currentHoverPanel:OnMouseLeave()
|
||||
end
|
||||
end
|
||||
currentHoverPanel = false
|
||||
cursor.Parent = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- RayPlaneIntersection
|
||||
|
||||
-- http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
|
||||
local function RayPlaneIntersection(ray, planeNormal, pointOnPlane)
|
||||
planeNormal = planeNormal.unit
|
||||
ray = ray.Unit
|
||||
-- compute Pn (dot) Rd = Vd and check if Vd == 0 then we know ray is parallel to plane
|
||||
local Vd = planeNormal:Dot(ray.Direction)
|
||||
|
||||
-- could fuzzy equals this a little bit to account for imprecision or very close angles to zero
|
||||
if Vd == 0 then -- parallel, no intersection
|
||||
return nil
|
||||
end
|
||||
|
||||
local V0 = planeNormal:Dot(pointOnPlane - ray.Origin)
|
||||
local t = V0 / Vd
|
||||
|
||||
if t < 0 then --plane is behind ray origin, and thus there is no intersection
|
||||
return nil
|
||||
end
|
||||
|
||||
return ray.Origin + ray.Direction * t
|
||||
end
|
||||
|
||||
function Panel3D.FindHoveredGuiElement(panel, elements)
|
||||
local cameraRenderCFrame = workspace.CurrentCamera and workspace.CurrentCamera:GetRenderCFrame()
|
||||
local panelPart = panel.part
|
||||
if cameraRenderCFrame and panelPart then
|
||||
local panelPartSize = panelPart.Size
|
||||
local panelSurfaceCFrame = panelPart.CFrame + panelPart.CFrame.lookVector * (panelPartSize.Z * 0.5)
|
||||
local intersectionPt = RayPlaneIntersection(Ray.new(cameraRenderCFrame.p, cameraRenderCFrame.lookVector), panelSurfaceCFrame.lookVector, panelSurfaceCFrame.p)
|
||||
if intersectionPt then
|
||||
local localPoint = panelSurfaceCFrame:pointToObjectSpace(intersectionPt) * Vector3.new(-1, 1, 1) + Vector3.new(panelPartSize.X/2, -panelPartSize.Y/2, 0)
|
||||
local guiPoint = Vector2.new((localPoint.X / panelPartSize.X) * panel.gui.AbsoluteSize.X, (localPoint.Y / panelPartSize.Y) * -panel.gui.AbsoluteSize.Y)
|
||||
|
||||
local x = guiPoint.x
|
||||
local y = guiPoint.y
|
||||
for _, item in pairs(elements) do
|
||||
local minPt = item.AbsolutePosition
|
||||
local maxPt = item.AbsolutePosition + item.AbsoluteSize
|
||||
if minPt.X <= x and maxPt.X >= x and minPt.Y <= y and maxPt.Y >= y then
|
||||
return item
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
game:GetService("RunService"):BindToRenderStep(renderStepName, Enum.RenderPriority.Last.Value, Panel3D.OnRenderStep)
|
||||
|
||||
local function OnCameraChanged(prop)
|
||||
if prop == "HeadScale" then
|
||||
pcall(function()
|
||||
headScale = workspace.CurrentCamera.HeadScale
|
||||
end)
|
||||
for i, v in pairs(panels) do
|
||||
v:Resize(v.width, v.height, v.pixelScale * PIXELS_PER_STUD)
|
||||
end
|
||||
end
|
||||
end
|
||||
local cameraChangedConn = nil
|
||||
workspace.Changed:connect(function(prop)
|
||||
if prop == "CurrentCamera" then
|
||||
OnCameraChanged("HeadScale")
|
||||
if cameraChangedConn then
|
||||
cameraChangedConn:disconnect()
|
||||
end
|
||||
cameraChangedConn = workspace.CurrentCamera.Changed:connect(OnCameraChanged)
|
||||
end
|
||||
end)
|
||||
if workspace.CurrentCamera then
|
||||
OnCameraChanged("HeadScale")
|
||||
cameraChangedConn = workspace.CurrentCamera.Changed:connect(OnCameraChanged)
|
||||
end
|
||||
|
||||
return Panel3D
|
||||
@@ -0,0 +1,628 @@
|
||||
--[[
|
||||
// FileName: PlayerDropDown.lua
|
||||
// Written by: TheGamer101
|
||||
// Description: Code for the player drop down in the PlayerList and Chat
|
||||
]]
|
||||
local moduleApiTable = {}
|
||||
|
||||
--[[ Services ]]--
|
||||
local CoreGui = game:GetService('CoreGui')
|
||||
local HttpService = game:GetService('HttpService')
|
||||
local HttpRbxApiService = game:GetService('HttpRbxApiService')
|
||||
local PlayersService = game:GetService('Players')
|
||||
|
||||
--[[ Script Variables ]]--
|
||||
local LocalPlayer = PlayersService.LocalPlayer
|
||||
|
||||
--[[ Constants ]]--
|
||||
local POPUP_ENTRY_SIZE_Y = 24
|
||||
local ENTRY_PAD = 2
|
||||
local BG_TRANSPARENCY = 0.5
|
||||
local BG_COLOR = Color3.new(31/255, 31/255, 31/255)
|
||||
local TEXT_STROKE_TRANSPARENCY = 0.75
|
||||
local TEXT_COLOR = Color3.new(1, 1, 243/255)
|
||||
local TEXT_STROKE_COLOR = Color3.new(34/255, 34/255, 34/255)
|
||||
local MAX_FRIEND_COUNT = 200
|
||||
local FRIEND_IMAGE = 'http://www.roblox.com/thumbs/avatar.ashx?userId='
|
||||
|
||||
--[[ Fast Flags ]]--
|
||||
local followerSuccess, isFollowersEnabled = pcall(function() return settings():GetFFlag("EnableLuaFollowers") end)
|
||||
local IsFollowersEnabled = followerSuccess and isFollowersEnabled
|
||||
|
||||
local serverFollowersSuccess, serverFollowersEnabled = pcall(function() return settings():GetFFlag("UserServerFollowers") end)
|
||||
local IsServerFollowers = serverFollowersSuccess and serverFollowersEnabled
|
||||
|
||||
--[[ Modules ]]--
|
||||
local RobloxGui = CoreGui:WaitForChild('RobloxGui')
|
||||
local reportAbuseMenu = require(RobloxGui.Modules.Settings.Pages.ReportAbuseMenu)
|
||||
|
||||
--[[ Bindables ]]--
|
||||
local BinbableFunction_SendNotification = nil
|
||||
spawn(function()
|
||||
BinbableFunction_SendNotification = RobloxGui:WaitForChild("SendNotification")
|
||||
end)
|
||||
|
||||
--[[ Remotes ]]--
|
||||
local RemoteEvent_NewFollower = nil
|
||||
|
||||
spawn(function()
|
||||
local RobloxReplicatedStorage = game:GetService('RobloxReplicatedStorage')
|
||||
RemoteEvent_NewFollower = RobloxReplicatedStorage:WaitForChild('NewFollower')
|
||||
end)
|
||||
|
||||
--[[ Utility Functions ]]--
|
||||
local function createSignal()
|
||||
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
|
||||
|
||||
--[[ Events ]]--
|
||||
local BlockStatusChanged = createSignal()
|
||||
|
||||
--[[ Personal Server Stuff ]]--
|
||||
local IsPersonalServer = false
|
||||
local PersonalServerService = nil
|
||||
if game.Workspace:FindFirstChild('PSVariable') then
|
||||
IsPersonalServer = true
|
||||
PersonalServerService = game:GetService('PersonalServerService')
|
||||
end
|
||||
game.Workspace.ChildAdded:connect(function(child)
|
||||
if child.Name == 'PSVariable' and child:IsA('BoolValue') then
|
||||
IsPersonalServer = true
|
||||
PersonalServerService = game:GetService('PersonalServerService')
|
||||
end
|
||||
end)
|
||||
|
||||
local PRIVILEGE_LEVEL = {
|
||||
OWNER = 255,
|
||||
ADMIN = 240,
|
||||
MEMBER = 128,
|
||||
VISITOR = 10,
|
||||
BANNED = 0,
|
||||
}
|
||||
|
||||
local function onPrivilegeLevelSelect(player, rank)
|
||||
while player.PersonalServerRank < rank do
|
||||
PersonalServerService:Promote(player)
|
||||
end
|
||||
while player.PersonalServerRank > rank do
|
||||
PersonalServerService:Demote(player)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Follower Notifications ]]--
|
||||
local function sendNotification(title, text, image, duration, callback)
|
||||
if BinbableFunction_SendNotification then
|
||||
BinbableFunction_SendNotification:Invoke(title, text, image, duration, callback)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Friend Functions ]]--
|
||||
local function getFriendStatus(selectedPlayer)
|
||||
if selectedPlayer == LocalPlayer then
|
||||
return Enum.FriendStatus.NotFriend
|
||||
else
|
||||
local success, result = pcall(function()
|
||||
-- NOTE: Core script only
|
||||
return LocalPlayer:GetFriendStatus(selectedPlayer)
|
||||
end)
|
||||
if success then
|
||||
return result
|
||||
else
|
||||
return Enum.FriendStatus.NotFriend
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if userId = nil, then it will get count for local player
|
||||
local function getFriendCountAsync(userId)
|
||||
local friendCount = nil
|
||||
local wasSuccess, result = pcall(function()
|
||||
local str = 'user/get-friendship-count'
|
||||
if userId then
|
||||
str = str..'?userId='..tostring(userId)
|
||||
end
|
||||
return HttpRbxApiService:GetAsync(str, true)
|
||||
end)
|
||||
if not wasSuccess then
|
||||
print("getFriendCountAsync() failed because", result)
|
||||
return nil
|
||||
end
|
||||
result = HttpService:JSONDecode(result)
|
||||
|
||||
if result["success"] and result["count"] then
|
||||
friendCount = result["count"]
|
||||
end
|
||||
|
||||
return friendCount
|
||||
end
|
||||
|
||||
-- checks if we can send a friend request. Right now the only way we
|
||||
-- can't is if one of the players is at the max friend limit
|
||||
local function canSendFriendRequestAsync(otherPlayer)
|
||||
local theirFriendCount = getFriendCountAsync(otherPlayer.userId)
|
||||
local myFriendCount = getFriendCountAsync()
|
||||
|
||||
-- assume max friends if web call fails
|
||||
if not myFriendCount or not theirFriendCount then
|
||||
return false
|
||||
end
|
||||
if myFriendCount < MAX_FRIEND_COUNT and theirFriendCount < MAX_FRIEND_COUNT then
|
||||
return true
|
||||
elseif myFriendCount >= MAX_FRIEND_COUNT then
|
||||
sendNotification("Cannot send friend request", "You are at the max friends limit.", "", 5, function() end)
|
||||
return false
|
||||
elseif theirFriendCount >= MAX_FRIEND_COUNT then
|
||||
sendNotification("Cannot send friend request", otherPlayer.Name.." is at the max friends limit.", "", 5, function() end)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Follower Functions ]]--
|
||||
|
||||
-- Returns whether followerUserId is following userId
|
||||
local function isFollowing(userId, followerUserId)
|
||||
local apiPath = "user/following-exists?userId="
|
||||
local params = userId.."&followerUserId="..followerUserId
|
||||
local success, result = pcall(function()
|
||||
return HttpRbxApiService:GetAsync(apiPath..params, true)
|
||||
end)
|
||||
if not success then
|
||||
print("isFollowing() failed because", result)
|
||||
return false
|
||||
end
|
||||
|
||||
-- can now parse web response
|
||||
result = HttpService:JSONDecode(result)
|
||||
return result["success"] and result["isFollowing"]
|
||||
end
|
||||
|
||||
--[[ Functions for Blocking users ]]--
|
||||
local BlockedList = {}
|
||||
local MutedList = {}
|
||||
|
||||
local function GetBlockedPlayersAsync()
|
||||
local userId = LocalPlayer.userId
|
||||
local apiPath = "userblock/getblockedusers" .. "?" .. "userId=" .. tostring(userId) .. "&" .. "page=" .. "1"
|
||||
if userId > 0 then
|
||||
local blockList = nil
|
||||
local success, msg = pcall(function()
|
||||
local request = HttpRbxApiService:GetAsync(apiPath)
|
||||
blockList = request and game:GetService('HttpService'):JSONDecode(request)
|
||||
end)
|
||||
if blockList and blockList['success'] == true and blockList['userList'] then
|
||||
return blockList['userList']
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
spawn(function()
|
||||
BlockedList = GetBlockedPlayersAsync()
|
||||
end)
|
||||
|
||||
local function isBlocked(userId)
|
||||
if (BlockedList[userId] ~= nil and BlockedList[userId] == true) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function isMuted(userId)
|
||||
if (MutedList[userId] ~= nil and MutedList[userId] == true) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function BlockPlayerAsync(playerToBlock)
|
||||
if playerToBlock and LocalPlayer ~= playerToBlock then
|
||||
local blockUserId = playerToBlock.UserId
|
||||
if blockUserId > 0 then
|
||||
if not isBlocked(blockUserId) then
|
||||
BlockedList[blockUserId] = true
|
||||
BlockStatusChanged:fire(blockUserId, true)
|
||||
pcall(function()
|
||||
local success = PlayersService:BlockUser(LocalPlayer.userId, blockUserId)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UnblockPlayerAsync(playerToUnblock)
|
||||
if playerToUnblock then
|
||||
local unblockUserId = playerToUnblock.userId
|
||||
|
||||
if isBlocked(unblockUserId) then
|
||||
BlockedList[unblockUserId] = nil
|
||||
BlockStatusChanged:fire(unblockUserId, false)
|
||||
pcall(function()
|
||||
local success = PlayersService:UnblockUser(LocalPlayer.userId, unblockUserId)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function MutePlayer(playerToMute)
|
||||
if playerToMute and LocalPlayer ~= playerToMute then
|
||||
local muteUserId = playerToMute.UserId
|
||||
if muteUserId > 0 then
|
||||
if not isMuted(muteUserId) then
|
||||
MutedList[muteUserId] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function UnmutePlayer(playerToUnmute)
|
||||
if playerToUnmute then
|
||||
local unmuteUserId = playerToUnmute.UserId
|
||||
MutedList[unmuteUserId] = nil
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Function to create DropDown class ]]--
|
||||
function createPlayerDropDown()
|
||||
local playerDropDown = {}
|
||||
playerDropDown.Player = nil
|
||||
playerDropDown.PopupFrame = nil
|
||||
playerDropDown.HidePopupImmediately = false
|
||||
playerDropDown.PopupFrameOffScreenPosition = nil -- if this is set the popup frame tweens to a different offscreen position than the default
|
||||
|
||||
playerDropDown.HiddenSignal = createSignal()
|
||||
|
||||
--[[ Functions for when options in the dropdown are pressed ]]--
|
||||
local function onFriendButtonPressed()
|
||||
if playerDropDown.Player then
|
||||
local status = getFriendStatus(playerDropDown.Player)
|
||||
if status == Enum.FriendStatus.Friend then
|
||||
LocalPlayer:RevokeFriendship(playerDropDown.Player)
|
||||
elseif status == Enum.FriendStatus.Unknown or status == Enum.FriendStatus.NotFriend then
|
||||
-- cache and spawn
|
||||
local cachedLastSelectedPlayer = playerDropDown.Player
|
||||
spawn(function()
|
||||
-- check for max friends before letting them send the request
|
||||
if canSendFriendRequestAsync(cachedLastSelectedPlayer) then -- Yields
|
||||
if cachedLastSelectedPlayer and cachedLastSelectedPlayer.Parent == PlayersService then
|
||||
LocalPlayer:RequestFriendship(cachedLastSelectedPlayer)
|
||||
end
|
||||
end
|
||||
end)
|
||||
elseif status == Enum.FriendStatus.FriendRequestSent then
|
||||
LocalPlayer:RevokeFriendship(playerDropDown.Player)
|
||||
elseif status == Enum.FriendStatus.FriendRequestReceived then
|
||||
LocalPlayer:RequestFriendship(playerDropDown.Player)
|
||||
end
|
||||
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function onDeclineFriendButonPressed()
|
||||
if playerDropDown.Player then
|
||||
LocalPlayer:RevokeFriendship(playerDropDown.Player)
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Client unfollows followedUserId
|
||||
local function onUnfollowButtonPressed()
|
||||
if not playerDropDown.Player then return end
|
||||
--
|
||||
local apiPath = "user/unfollow"
|
||||
local params = "followedUserId="..tostring(playerDropDown.Player.userId)
|
||||
local success, result = pcall(function()
|
||||
return HttpRbxApiService:PostAsync(apiPath, params, true, Enum.ThrottlingPriority.Default, Enum.HttpContentType.ApplicationUrlEncoded)
|
||||
end)
|
||||
if not success then
|
||||
print("unfollowPlayer() failed because", result)
|
||||
playerDropDown:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
result = HttpService:JSONDecode(result)
|
||||
if result["success"] then
|
||||
if RemoteEvent_NewFollower then
|
||||
RemoteEvent_NewFollower:FireServer(playerDropDown.Player, false)
|
||||
end
|
||||
moduleApiTable.FollowerStatusChanged:fire()
|
||||
end
|
||||
|
||||
playerDropDown:Hide()
|
||||
-- no need to send notification when someone unfollows
|
||||
end
|
||||
|
||||
local function onBlockButtonPressed()
|
||||
if playerDropDown.Player then
|
||||
local cachedPlayer = playerDropDown.Player
|
||||
spawn(function()
|
||||
BlockPlayerAsync(cachedPlayer)
|
||||
end)
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function onUnblockButtonPressed()
|
||||
if playerDropDown.Player then
|
||||
local cachedPlayer = playerDropDown.Player
|
||||
spawn(function()
|
||||
UnblockPlayerAsync(cachedPlayer)
|
||||
end)
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
local function onReportButtonPressed()
|
||||
if playerDropDown.Player then
|
||||
reportAbuseMenu:ReportPlayer(playerDropDown.Player)
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
-- Client follows followedUserId
|
||||
local function onFollowButtonPressed()
|
||||
if not playerDropDown.Player then return end
|
||||
--
|
||||
local followedUserId = tostring(playerDropDown.Player.userId)
|
||||
local apiPath = "user/follow"
|
||||
local params = "followedUserId="..followedUserId
|
||||
local success, result = pcall(function()
|
||||
return HttpRbxApiService:PostAsync(apiPath, params, true, Enum.ThrottlingPriority.Default, Enum.HttpContentType.ApplicationUrlEncoded)
|
||||
end)
|
||||
if not success then
|
||||
print("followPlayer() failed because", result)
|
||||
playerDropDown:Hide()
|
||||
return
|
||||
end
|
||||
|
||||
result = HttpService:JSONDecode(result)
|
||||
if result["success"] then
|
||||
sendNotification("You are", "now following "..playerDropDown.Player.Name, FRIEND_IMAGE..followedUserId.."&x=48&y=48", 5, function() end)
|
||||
if RemoteEvent_NewFollower then
|
||||
RemoteEvent_NewFollower:FireServer(playerDropDown.Player, true)
|
||||
end
|
||||
moduleApiTable.FollowerStatusChanged:fire()
|
||||
end
|
||||
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
|
||||
--[[ GUI Creation Functions ]]--
|
||||
local function createPersonalServerDialog(buttons, selectedPlayer)
|
||||
local showPersonalServerRanks = IsPersonalServer and LocalPlayer.PersonalServerRank >= PRIVILEGE_LEVEL.ADMIN and LocalPlayer.PersonalServerRank > selectedPlayer.PersonalServerRank
|
||||
if showPersonalServerRanks then
|
||||
table.insert(buttons, {
|
||||
Name = "BanButton",
|
||||
Text = "Ban",
|
||||
OnPress = function()
|
||||
playerDropDown:Hide()
|
||||
onPrivilegeLevelSelect(selectedPlayer, PRIVILEGE_LEVEL.BANNED)
|
||||
end,
|
||||
})
|
||||
table.insert(buttons, {
|
||||
Name = "VistorButton",
|
||||
Text = "Visitor",
|
||||
OnPress = function()
|
||||
onPrivilegeLevelSelect(selectedPlayer, PRIVILEGE_LEVEL.VISITOR)
|
||||
end,
|
||||
})
|
||||
table.insert(buttons, {
|
||||
Name = "MemberButton",
|
||||
Text = "Member",
|
||||
OnPress = function()
|
||||
onPrivilegeLevelSelect(selectedPlayer, PRIVILEGE_LEVEL.MEMBER)
|
||||
end,
|
||||
})
|
||||
table.insert(buttons, {
|
||||
Name = "AdminButton",
|
||||
Text = "Admin",
|
||||
OnPress = function()
|
||||
onPrivilegeLevelSelect(selectedPlayer, PRIVILEGE_LEVEL.ADMIN)
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function createPopupFrame(buttons)
|
||||
local frame = Instance.new('Frame')
|
||||
frame.Name = "PopupFrame"
|
||||
frame.Size = UDim2.new(1, 0, 0, (POPUP_ENTRY_SIZE_Y * #buttons) + (#buttons - ENTRY_PAD))
|
||||
frame.Position = UDim2.new(1, 1, 0, 0)
|
||||
frame.BackgroundTransparency = 1
|
||||
|
||||
for i,button in ipairs(buttons) do
|
||||
local btn = Instance.new('TextButton')
|
||||
btn.Name = button.Name
|
||||
btn.Size = UDim2.new(1, 0, 0, POPUP_ENTRY_SIZE_Y)
|
||||
btn.Position = UDim2.new(0, 0, 0, POPUP_ENTRY_SIZE_Y * (i - 1) + ((i - 1) * ENTRY_PAD))
|
||||
btn.BackgroundTransparency = BG_TRANSPARENCY
|
||||
btn.BackgroundColor3 = BG_COLOR
|
||||
btn.BorderSizePixel = 0
|
||||
btn.Text = button.Text
|
||||
btn.Font = Enum.Font.SourceSans
|
||||
btn.FontSize = Enum.FontSize.Size14
|
||||
btn.TextColor3 = TEXT_COLOR
|
||||
btn.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY
|
||||
btn.TextStrokeColor3 = TEXT_STROKE_COLOR
|
||||
btn.AutoButtonColor = true
|
||||
btn.Parent = frame
|
||||
|
||||
btn.MouseButton1Click:connect(button.OnPress)
|
||||
end
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
--[[ PlayerDropDown Functions ]]--
|
||||
function playerDropDown:Hide()
|
||||
if playerDropDown.PopupFrame then
|
||||
local offscreenPosition = (playerDropDown.PopupFrameOffScreenPosition ~= nil and playerDropDown.PopupFrameOffScreenPosition or UDim2.new(1, 1, 0, playerDropDown.PopupFrame.Position.Y.Offset))
|
||||
if not playerDropDown.HidePopupImmediately then
|
||||
playerDropDown.PopupFrame:TweenPosition(offscreenPosition, Enum.EasingDirection.InOut,
|
||||
Enum.EasingStyle.Quad, TWEEN_TIME, true, function()
|
||||
if playerDropDown.PopupFrame then
|
||||
playerDropDown.PopupFrame:Destroy()
|
||||
playerDropDown.PopupFrame = nil
|
||||
end
|
||||
end)
|
||||
else
|
||||
playerDropDown.PopupFrame:Destroy()
|
||||
playerDropDown.PopupFrame = nil
|
||||
end
|
||||
end
|
||||
if playerDropDown.Player then
|
||||
playerDropDown.Player = nil
|
||||
end
|
||||
playerDropDown.HiddenSignal:fire()
|
||||
end
|
||||
|
||||
function playerDropDown:CreatePopup(Player)
|
||||
playerDropDown.Player = Player
|
||||
|
||||
local buttons = {}
|
||||
|
||||
local status = getFriendStatus(playerDropDown.Player)
|
||||
local friendText = ""
|
||||
local canDeclineFriend = false
|
||||
if status == Enum.FriendStatus.Friend then
|
||||
friendText = "Unfriend Player"
|
||||
elseif status == Enum.FriendStatus.Unknown or status == Enum.FriendStatus.NotFriend then
|
||||
friendText = "Send Friend Request"
|
||||
elseif status == Enum.FriendStatus.FriendRequestSent then
|
||||
friendText = "Revoke Friend Request"
|
||||
elseif status == Enum.FriendStatus.FriendRequestReceived then
|
||||
friendText = "Accept Friend Request"
|
||||
canDeclineFriend = true
|
||||
end
|
||||
|
||||
local blocked = isBlocked(playerDropDown.Player.userId)
|
||||
|
||||
if not blocked then
|
||||
table.insert(buttons, {
|
||||
Name = "FriendButton",
|
||||
Text = friendText,
|
||||
OnPress = onFriendButtonPressed,
|
||||
})
|
||||
end
|
||||
|
||||
if canDeclineFriend and not blocked then
|
||||
table.insert(buttons, {
|
||||
Name = "DeclineFriend",
|
||||
Text = "Decline Friend Request",
|
||||
OnPress = onDeclineFriendButonPressed,
|
||||
})
|
||||
end
|
||||
-- following status
|
||||
if IsServerFollowers or IsFollowersEnabled then
|
||||
local following = isFollowing(playerDropDown.Player.userId, LocalPlayer.userId)
|
||||
local followerText = following and "Unfollow Player" or "Follow Player"
|
||||
|
||||
if not blocked then
|
||||
table.insert(buttons, {
|
||||
Name = "FollowerButton",
|
||||
Text = followerText,
|
||||
OnPress = following and onUnfollowButtonPressed or onFollowButtonPressed,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local blockedText = blocked and "Unblock Player" or "Block Player"
|
||||
table.insert(buttons, {
|
||||
Name = "BlockButton",
|
||||
Text = blockedText,
|
||||
OnPress = blocked and onUnblockButtonPressed or onBlockButtonPressed,
|
||||
})
|
||||
table.insert(buttons, {
|
||||
Name = "ReportButton",
|
||||
Text = "Report Abuse",
|
||||
OnPress = onReportButtonPressed,
|
||||
})
|
||||
|
||||
createPersonalServerDialog(buttons, playerDropDown.Player)
|
||||
if playerDropDown.PopupFrame then
|
||||
playerDropDown.PopupFrame:Destroy()
|
||||
end
|
||||
playerDropDown.PopupFrame = createPopupFrame(buttons)
|
||||
return playerDropDown.PopupFrame
|
||||
end
|
||||
|
||||
--[[ PlayerRemoving Connection ]]--
|
||||
PlayersService.PlayerRemoving:connect(function(leavingPlayer)
|
||||
if playerDropDown.Player == leavingPlayer then
|
||||
playerDropDown:Hide()
|
||||
end
|
||||
end)
|
||||
|
||||
return playerDropDown
|
||||
end
|
||||
|
||||
|
||||
do
|
||||
moduleApiTable.FollowerStatusChanged = createSignal()
|
||||
|
||||
function moduleApiTable:CreatePlayerDropDown()
|
||||
return createPlayerDropDown()
|
||||
end
|
||||
|
||||
function moduleApiTable:CreateBlockingUtility()
|
||||
local blockingUtility = {}
|
||||
|
||||
function blockingUtility:BlockPlayerAsync(player)
|
||||
return BlockPlayerAsync(player)
|
||||
end
|
||||
|
||||
function blockingUtility:UnblockPlayerAsync(player)
|
||||
return UnblockPlayerAsync(player)
|
||||
end
|
||||
|
||||
function blockingUtility:MutePlayer(player)
|
||||
return MutePlayer(player)
|
||||
end
|
||||
|
||||
function blockingUtility:UnmutePlayer(player)
|
||||
return UnmutePlayer(player)
|
||||
end
|
||||
|
||||
function blockingUtility:IsPlayerBlockedByUserId(userId)
|
||||
return isBlocked(userId)
|
||||
end
|
||||
|
||||
function blockingUtility:GetBlockedStatusChangedEvent()
|
||||
return BlockStatusChanged
|
||||
end
|
||||
|
||||
function blockingUtility:IsPlayerMutedByUserId(userId)
|
||||
return isMuted(userId)
|
||||
end
|
||||
|
||||
return blockingUtility
|
||||
end
|
||||
end
|
||||
|
||||
return moduleApiTable
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,790 @@
|
||||
--[[
|
||||
Filename: GameSettings.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.1
|
||||
Description: Takes care of the Game Settings Tab in Settings Menu
|
||||
--]]
|
||||
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local PlatformService = nil
|
||||
pcall(function() PlatformService = game:GetService("PlatformService") end)
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
local Settings = UserSettings()
|
||||
local GameSettings = Settings.GameSettings
|
||||
|
||||
-------------- CONSTANTS --------------
|
||||
local GRAPHICS_QUALITY_LEVELS = 10
|
||||
local GRAPHICS_QUALITY_TO_INT = {
|
||||
["Enum.SavedQualitySetting.Automatic"] = 0,
|
||||
["Enum.SavedQualitySetting.QualityLevel1"] = 1,
|
||||
["Enum.SavedQualitySetting.QualityLevel2"] = 2,
|
||||
["Enum.SavedQualitySetting.QualityLevel3"] = 3,
|
||||
["Enum.SavedQualitySetting.QualityLevel4"] = 4,
|
||||
["Enum.SavedQualitySetting.QualityLevel5"] = 5,
|
||||
["Enum.SavedQualitySetting.QualityLevel6"] = 6,
|
||||
["Enum.SavedQualitySetting.QualityLevel7"] = 7,
|
||||
["Enum.SavedQualitySetting.QualityLevel8"] = 8,
|
||||
["Enum.SavedQualitySetting.QualityLevel9"] = 9,
|
||||
["Enum.SavedQualitySetting.QualityLevel10"] = 10,
|
||||
}
|
||||
local PC_CHANGED_PROPS = {
|
||||
DevComputerMovementMode = true,
|
||||
DevComputerCameraMode = true,
|
||||
DevEnableMouseLock = true,
|
||||
}
|
||||
local TOUCH_CHANGED_PROPS = {
|
||||
DevTouchMovementMode = true,
|
||||
DevTouchCameraMode = true,
|
||||
}
|
||||
local CAMERA_MODE_DEFAULT_STRING = UserInputService.TouchEnabled and "Default (Follow)" or "Default (Classic)"
|
||||
|
||||
local MOVEMENT_MODE_DEFAULT_STRING = UserInputService.TouchEnabled and "Default (Thumbstick)" or "Default (Keyboard)"
|
||||
local MOVEMENT_MODE_KEYBOARDMOUSE_STRING = "Keyboard + Mouse"
|
||||
local MOVEMENT_MODE_CLICKTOMOVE_STRING = UserInputService.TouchEnabled and "Tap to Move" or "Click to Move"
|
||||
|
||||
----------- UTILITIES --------------
|
||||
local utility = require(RobloxGui.Modules.Settings.Utility)
|
||||
|
||||
------------ Variables -------------------
|
||||
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
|
||||
RobloxGui:WaitForChild("Modules"):WaitForChild("Settings"):WaitForChild("SettingsHub")
|
||||
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
|
||||
local HasVRAPI = false
|
||||
pcall(function() HasVRAPI = UserInputService.GetUserCFrame ~= nil end)
|
||||
local PageInstance = nil
|
||||
local LocalPlayer = game.Players.LocalPlayer
|
||||
local platform = UserInputService:GetPlatform()
|
||||
local overscanScreen = nil
|
||||
|
||||
----------- CLASS DECLARATION --------------
|
||||
|
||||
local function Initialize()
|
||||
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
|
||||
local this = settingsPageFactory:CreateNewPage()
|
||||
|
||||
----------- FUNCTIONS ---------------
|
||||
local function createGraphicsOptions()
|
||||
|
||||
------------------ Fullscreen Selection GUI Setup ------------------
|
||||
local fullScreenInit = 1
|
||||
if not GameSettings:InFullScreen() then
|
||||
fullScreenInit = 2
|
||||
end
|
||||
|
||||
this.FullscreenFrame,
|
||||
this.FullscreenLabel,
|
||||
this.FullscreenEnabler = utility:AddNewRow(this, "Fullscreen", "Selector", {"On", "Off"}, fullScreenInit)
|
||||
|
||||
local fullScreenSelectionFrame = this.FullscreenEnabler.SliderFrame and this.FullscreenEnabler.SliderFrame or this.FullscreenEnabler.SelectorFrame
|
||||
|
||||
this.FullscreenEnabler.IndexChanged:connect(function(newIndex)
|
||||
GuiService:ToggleFullscreen()
|
||||
end)
|
||||
|
||||
------------------ Gfx Enabler Selection GUI Setup ------------------
|
||||
local graphicsEnablerStart = 1
|
||||
if GameSettings.SavedQualityLevel ~= Enum.SavedQualitySetting.Automatic then
|
||||
graphicsEnablerStart = 2
|
||||
end
|
||||
|
||||
this.GraphicsEnablerFrame,
|
||||
this.GraphicsEnablerLabel,
|
||||
this.GraphicsQualityEnabler = utility:AddNewRow(this, "Graphics Mode", "Selector", {"Automatic", "Manual"}, graphicsEnablerStart)
|
||||
|
||||
------------------ Gfx Slider GUI Setup ------------------
|
||||
this.GraphicsQualityFrame,
|
||||
this.GraphicsQualityLabel,
|
||||
this.GraphicsQualitySlider = utility:AddNewRow(this, "Graphics Quality", "Slider", GRAPHICS_QUALITY_LEVELS, 1)
|
||||
this.GraphicsQualitySlider:SetMinStep(1)
|
||||
|
||||
------------------------- Connection Setup ----------------------------
|
||||
settings().Rendering.EnableFRM = true
|
||||
|
||||
function SetGraphicsQuality(newValue, automaticSettingAllowed)
|
||||
local percentage = newValue/GRAPHICS_QUALITY_LEVELS
|
||||
local newQualityLevel = math.floor((settings().Rendering:GetMaxQualityLevel() - 1) * percentage)
|
||||
if newQualityLevel == 20 then
|
||||
newQualityLevel = 21
|
||||
elseif newValue == 1 then
|
||||
newQualityLevel = 1
|
||||
elseif newValue < 1 and not automaticSettingAllowed then
|
||||
newValue = 1
|
||||
newQualityLevel = 1
|
||||
elseif newQualityLevel > settings().Rendering:GetMaxQualityLevel() then
|
||||
newQualityLevel = settings().Rendering:GetMaxQualityLevel() - 1
|
||||
end
|
||||
|
||||
GameSettings.SavedQualityLevel = newValue
|
||||
settings().Rendering.QualityLevel = newQualityLevel
|
||||
end
|
||||
|
||||
local function setGraphicsToAuto()
|
||||
this.GraphicsQualitySlider:SetZIndex(1)
|
||||
this.GraphicsQualityLabel.ZIndex = 1
|
||||
this.GraphicsQualitySlider:SetInteractable(false)
|
||||
|
||||
SetGraphicsQuality(Enum.QualityLevel.Automatic.Value, true)
|
||||
end
|
||||
local function setGraphicsToManual(level)
|
||||
this.GraphicsQualitySlider:SetZIndex(2)
|
||||
this.GraphicsQualityLabel.ZIndex = 2
|
||||
this.GraphicsQualitySlider:SetInteractable(true)
|
||||
|
||||
-- need to force the quality change if slider is already at this position
|
||||
if this.GraphicsQualitySlider:GetValue() == level then
|
||||
SetGraphicsQuality(level)
|
||||
else
|
||||
this.GraphicsQualitySlider:SetValue(level)
|
||||
end
|
||||
end
|
||||
|
||||
game.GraphicsQualityChangeRequest:connect(function(isIncrease)
|
||||
if settings().Rendering.QualityLevel == Enum.QualityLevel.Automatic then return end
|
||||
--
|
||||
local currentGraphicsSliderValue = this.GraphicsQualitySlider:GetValue()
|
||||
if isIncrease then
|
||||
currentGraphicsSliderValue = currentGraphicsSliderValue + 1
|
||||
else
|
||||
currentGraphicsSliderValue = currentGraphicsSliderValue - 1
|
||||
end
|
||||
|
||||
this.GraphicsQualitySlider:SetValue(currentGraphicsSliderValue)
|
||||
end)
|
||||
|
||||
this.GraphicsQualitySlider.ValueChanged:connect(function(newValue)
|
||||
SetGraphicsQuality(newValue)
|
||||
end)
|
||||
|
||||
this.GraphicsQualityEnabler.IndexChanged:connect(function(newIndex)
|
||||
if newIndex == 1 then
|
||||
setGraphicsToAuto()
|
||||
elseif newIndex == 2 then
|
||||
setGraphicsToManual( this.GraphicsQualitySlider:GetValue() )
|
||||
end
|
||||
end)
|
||||
|
||||
-- initialize the slider position
|
||||
if GameSettings.SavedQualityLevel == Enum.SavedQualitySetting.Automatic then
|
||||
this.GraphicsQualitySlider:SetValue(5)
|
||||
setGraphicsToAuto()
|
||||
else
|
||||
local graphicsLevel = tostring(GameSettings.SavedQualityLevel)
|
||||
if GRAPHICS_QUALITY_TO_INT[graphicsLevel] then
|
||||
graphicsLevel = GRAPHICS_QUALITY_TO_INT[graphicsLevel]
|
||||
else
|
||||
graphicsLevel = GRAPHICS_QUALITY_LEVELS
|
||||
end
|
||||
|
||||
spawn(function()
|
||||
this.GraphicsQualitySlider:SetValue(graphicsLevel)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function createCameraModeOptions(movementModeEnabled)
|
||||
------------------------------------------------------
|
||||
------------------
|
||||
------------------ Shift Lock Switch -----------------
|
||||
if UserInputService.MouseEnabled then
|
||||
this.ShiftLockFrame,
|
||||
this.ShiftLockLabel,
|
||||
this.ShiftLockMode,
|
||||
this.ShiftLockOverrideText = nil
|
||||
|
||||
if UserInputService.MouseEnabled and UserInputService.KeyboardEnabled then
|
||||
local startIndex = 2
|
||||
if GameSettings.ControlMode == Enum.ControlMode.MouseLockSwitch then
|
||||
startIndex = 1
|
||||
end
|
||||
|
||||
this.ShiftLockFrame,
|
||||
this.ShiftLockLabel,
|
||||
this.ShiftLockMode = utility:AddNewRow(this, "Shift Lock Switch", "Selector", {"On", "Off"}, startIndex)
|
||||
|
||||
this.ShiftLockOverrideText = utility:Create'TextLabel'
|
||||
{
|
||||
Name = "ShiftLockOverrideLabel",
|
||||
Text = "Set by Developer",
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
FontSize = Enum.FontSize.Size24,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0,200,1,0),
|
||||
Position = UDim2.new(1,-350,0,0),
|
||||
Visible = false,
|
||||
ZIndex = 2,
|
||||
Parent = this.ShiftLockFrame
|
||||
};
|
||||
|
||||
this.ShiftLockMode.IndexChanged:connect(function(newIndex)
|
||||
if newIndex == 1 then
|
||||
GameSettings.ControlMode = Enum.ControlMode.MouseLockSwitch
|
||||
else
|
||||
GameSettings.ControlMode = Enum.ControlMode.Classic
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
------------------
|
||||
------------------ Camera Mode -----------------------
|
||||
do
|
||||
local enumItems = nil
|
||||
local startingCameraEnumItem = 1
|
||||
if UserInputService.TouchEnabled then
|
||||
enumItems = Enum.TouchCameraMovementMode:GetEnumItems()
|
||||
else
|
||||
enumItems = Enum.ComputerCameraMovementMode:GetEnumItems()
|
||||
end
|
||||
|
||||
local cameraEnumNames = {}
|
||||
local cameraEnumNameToItem = {}
|
||||
for i = 1, #enumItems do
|
||||
local displayName = enumItems[i].Name
|
||||
if displayName == 'Default' then
|
||||
displayName = CAMERA_MODE_DEFAULT_STRING
|
||||
end
|
||||
|
||||
if UserInputService.TouchEnabled then
|
||||
if GameSettings.TouchCameraMovementMode == enumItems[i] then
|
||||
startingCameraEnumItem = i
|
||||
end
|
||||
else
|
||||
if GameSettings.ComputerCameraMovementMode == enumItems[i] then
|
||||
startingCameraEnumItem = i
|
||||
end
|
||||
end
|
||||
|
||||
cameraEnumNames[i] = displayName
|
||||
cameraEnumNameToItem[displayName] = enumItems[i].Value
|
||||
end
|
||||
|
||||
this.CameraModeFrame,
|
||||
this.CameraModeLabel,
|
||||
this.CameraMode = utility:AddNewRow(this, "Camera Mode", "Selector", cameraEnumNames, startingCameraEnumItem)
|
||||
|
||||
this.CameraModeOverrideText = utility:Create'TextLabel'
|
||||
{
|
||||
Name = "CameraDevOverrideLabel",
|
||||
Text = "Set by Developer",
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
FontSize = Enum.FontSize.Size24,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0,200,1,0),
|
||||
Position = UDim2.new(1,-350,0,0),
|
||||
Visible = false,
|
||||
ZIndex = 2,
|
||||
Parent = this.CameraModeFrame
|
||||
};
|
||||
|
||||
this.CameraMode.IndexChanged:connect(function(newIndex)
|
||||
local newEnumSetting = cameraEnumNameToItem[cameraEnumNames[newIndex]]
|
||||
|
||||
if UserInputService.TouchEnabled then
|
||||
GameSettings.TouchCameraMovementMode = newEnumSetting
|
||||
else
|
||||
GameSettings.ComputerCameraMovementMode = newEnumSetting
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
------------------
|
||||
------------------ VR Camera Mode -----------------------
|
||||
|
||||
if HasVRAPI and UserInputService.VREnabled then
|
||||
local VR_ROTATION_INTENSITY_OPTIONS = {"Low", "High", "Smooth"}
|
||||
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.VRRotationFrame,
|
||||
this.VRRotationLabel,
|
||||
this.VRRotationMode = utility:AddNewRow(this, "VR Camera Rotation", "Selector", VR_ROTATION_INTENSITY_OPTIONS, GameSettings.VRRotationIntensity)
|
||||
else
|
||||
this.VRRotationFrame,
|
||||
this.VRRotationLabel,
|
||||
this.VRRotationMode = utility:AddNewRow(this, "VR Camera Rotation", "Selector", VR_ROTATION_INTENSITY_OPTIONS, GameSettings.VRRotationIntensity, 3)
|
||||
end
|
||||
|
||||
StarterGui:RegisterGetCore("VRRotationIntensity",
|
||||
function()
|
||||
return VR_ROTATION_INTENSITY_OPTIONS[GameSettings.VRRotationIntensity] or VR_ROTATION_INTENSITY_OPTIONS[1]
|
||||
end)
|
||||
this.VRRotationMode.IndexChanged:connect(function(newIndex)
|
||||
GameSettings.VRRotationIntensity = newIndex
|
||||
end)
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
------------------
|
||||
------------------ Movement Mode ---------------------
|
||||
if movementModeEnabled then
|
||||
local movementEnumItems = nil
|
||||
local startingMovementEnumItem = 1
|
||||
if UserInputService.TouchEnabled then
|
||||
movementEnumItems = Enum.TouchMovementMode:GetEnumItems()
|
||||
else
|
||||
movementEnumItems = Enum.ComputerMovementMode:GetEnumItems()
|
||||
end
|
||||
|
||||
local movementEnumNames = {}
|
||||
local movementEnumNameToItem = {}
|
||||
for i = 1, #movementEnumItems do
|
||||
local displayName = movementEnumItems[i].Name
|
||||
if displayName == "Default" then
|
||||
displayName = MOVEMENT_MODE_DEFAULT_STRING
|
||||
elseif displayName == "KeyboardMouse" then
|
||||
displayName = MOVEMENT_MODE_KEYBOARDMOUSE_STRING
|
||||
elseif displayName == "ClickToMove" then
|
||||
displayName = MOVEMENT_MODE_CLICKTOMOVE_STRING
|
||||
end
|
||||
|
||||
if UserInputService.TouchEnabled then
|
||||
if GameSettings.TouchMovementMode == movementEnumItems[i] then
|
||||
startingMovementEnumItem = i
|
||||
end
|
||||
else
|
||||
if GameSettings.ComputerMovementMode == movementEnumItems[i] then
|
||||
startingMovementEnumItem = i
|
||||
end
|
||||
end
|
||||
|
||||
movementEnumNames[i] = displayName
|
||||
movementEnumNameToItem[displayName] = movementEnumItems[i]
|
||||
end
|
||||
|
||||
this.MovementModeFrame,
|
||||
this.MovementModeLabel,
|
||||
this.MovementMode = utility:AddNewRow(this, "Movement Mode", "Selector", movementEnumNames, startingMovementEnumItem)
|
||||
|
||||
this.MovementModeOverrideText = utility:Create'TextLabel'
|
||||
{
|
||||
Name = "MovementDevOverrideLabel",
|
||||
Text = "Set by Developer",
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Font = Enum.Font.SourceSans,
|
||||
FontSize = Enum.FontSize.Size24,
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(0,200,1,0),
|
||||
Position = UDim2.new(1,-350,0,0),
|
||||
Visible = false,
|
||||
ZIndex = 2,
|
||||
Parent = this.MovementModeFrame
|
||||
};
|
||||
|
||||
this.MovementMode.IndexChanged:connect(function(newIndex)
|
||||
local newEnumSetting = movementEnumNameToItem[movementEnumNames[newIndex]]
|
||||
|
||||
if UserInputService.TouchEnabled then
|
||||
GameSettings.TouchMovementMode = newEnumSetting
|
||||
else
|
||||
GameSettings.ComputerMovementMode = newEnumSetting
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
------------------------------------------------------
|
||||
------------------
|
||||
------------------------- Connection Setup -----------
|
||||
function setCameraModeVisible(visible)
|
||||
if this.CameraMode then
|
||||
this.CameraMode.SelectorFrame.Visible = visible
|
||||
this.CameraMode:SetInteractable(visible)
|
||||
end
|
||||
end
|
||||
|
||||
function setMovementModeVisible(visible)
|
||||
if this.MovementMode then
|
||||
this.MovementMode.SelectorFrame.Visible = visible
|
||||
this.MovementMode:SetInteractable(visible)
|
||||
end
|
||||
end
|
||||
|
||||
function setShiftLockVisible(visible)
|
||||
if this.ShiftLockMode then
|
||||
this.ShiftLockMode.SelectorFrame.Visible = visible
|
||||
this.ShiftLockMode:SetInteractable(visible)
|
||||
end
|
||||
end
|
||||
|
||||
do -- initial set of dev vs user choice for guis
|
||||
local isUserChoiceCamera = false
|
||||
if UserInputService.TouchEnabled then
|
||||
isUserChoiceCamera = LocalPlayer.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice
|
||||
else
|
||||
isUserChoiceCamera = LocalPlayer.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice
|
||||
end
|
||||
|
||||
if not isUserChoiceCamera then
|
||||
this.CameraModeOverrideText.Visible = true
|
||||
setCameraModeVisible(false)
|
||||
else
|
||||
this.CameraModeOverrideText.Visible = false
|
||||
setCameraModeVisible(true)
|
||||
end
|
||||
|
||||
|
||||
local isUserChoiceMovement = false
|
||||
if UserInputService.TouchEnabled then
|
||||
isUserChoiceMovement = LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice
|
||||
else
|
||||
isUserChoiceMovement = LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice
|
||||
end
|
||||
|
||||
if this.MovementModeOverrideText then
|
||||
if not isUserChoiceMovement then
|
||||
this.MovementModeOverrideText.Visible = true
|
||||
setMovementModeVisible(false)
|
||||
else
|
||||
this.MovementModeOverrideText.Visible = false
|
||||
setMovementModeVisible(true)
|
||||
end
|
||||
end
|
||||
|
||||
if this.ShiftLockOverrideText then
|
||||
this.ShiftLockOverrideText.Visible = not LocalPlayer.DevEnableMouseLock
|
||||
setShiftLockVisible(LocalPlayer.DevEnableMouseLock)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateUserSettingsMenu(property)
|
||||
if this.ShiftLockOverrideText and property == "DevEnableMouseLock" then
|
||||
this.ShiftLockOverrideText.Visible = not LocalPlayer.DevEnableMouseLock
|
||||
setShiftLockVisible(LocalPlayer.DevEnableMouseLock)
|
||||
elseif property == "DevComputerCameraMode" then
|
||||
local isUserChoice = LocalPlayer.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice
|
||||
setCameraModeVisible(isUserChoice)
|
||||
this.CameraModeOverrideText.Visible = not isUserChoice
|
||||
elseif property == "DevComputerMovementMode" then
|
||||
local isUserChoice = LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice
|
||||
setMovementModeVisible(isUserChoice)
|
||||
if this.MovementModeOverrideText then
|
||||
this.MovementModeOverrideText.Visible = not isUserChoice
|
||||
end
|
||||
-- TOUCH
|
||||
elseif property == "DevTouchMovementMode" then
|
||||
local isUserChoice = LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice
|
||||
setMovementModeVisible(isUserChoice)
|
||||
if this.MovementModeOverrideText then
|
||||
this.MovementModeOverrideText.Visible = not isUserChoice
|
||||
end
|
||||
elseif property == "DevTouchCameraMode" then
|
||||
local isUserChoice = LocalPlayer.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice
|
||||
setCameraModeVisible(isUserChoice)
|
||||
this.CameraModeOverrideText.Visible = not isUserChoice
|
||||
end
|
||||
end
|
||||
|
||||
LocalPlayer.Changed:connect(function(property)
|
||||
if IsTouchClient then
|
||||
if TOUCH_CHANGED_PROPS[property] then
|
||||
updateUserSettingsMenu(property)
|
||||
end
|
||||
else
|
||||
if PC_CHANGED_PROPS[property] then
|
||||
updateUserSettingsMenu(property)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function createVolumeOptions()
|
||||
local startVolumeLevel = math.floor(GameSettings.MasterVolume * 10)
|
||||
this.VolumeFrame,
|
||||
this.VolumeLabel,
|
||||
this.VolumeSlider = utility:AddNewRow(this, "Volume", "Slider", 10, startVolumeLevel)
|
||||
|
||||
local volumeSound = Instance.new("Sound", game.CoreGui.RobloxGui.Sounds)
|
||||
volumeSound.Name = "VolumeChangeSound"
|
||||
volumeSound.SoundId = "rbxasset://sounds/metalstone2.mp3"
|
||||
|
||||
this.VolumeSlider.ValueChanged:connect(function(newValue)
|
||||
local soundPercent = newValue/10
|
||||
volumeSound.Volume = soundPercent
|
||||
volumeSound:Play()
|
||||
GameSettings.MasterVolume = soundPercent
|
||||
end)
|
||||
end
|
||||
|
||||
local function createMouseOptions()
|
||||
local MouseSteps = 10
|
||||
local MinMouseSensitivity = 0.2
|
||||
local AdvancedSuccess, AdvancedValue = pcall(function() return settings():GetFFlag("AdvancedMouseSensitivityEnabled") end)
|
||||
local AdvancedEnabled = AdvancedSuccess and AdvancedValue
|
||||
|
||||
-- equations below map a function to include points (0, 0.2) (5, 1) (10, 4)
|
||||
-- where x is the slider position, y is the mouse sensitivity
|
||||
local function translateEngineMouseSensitivityToGui(engineSensitivity)
|
||||
return math.floor((2.0/3.0) * (math.sqrt(75.0 * engineSensitivity - 11.0) - 2))
|
||||
end
|
||||
|
||||
local function translateGuiMouseSensitivityToEngine(guiSensitivity)
|
||||
return 0.03 * math.pow(guiSensitivity,2) + (0.08 * guiSensitivity) + MinMouseSensitivity
|
||||
end
|
||||
|
||||
local startMouseLevel = translateEngineMouseSensitivityToGui(GameSettings.MouseSensitivity)
|
||||
|
||||
------------------ Mouse Selection GUI Setup ------------------
|
||||
-- switch between basic mode and advanced mode.
|
||||
local MouseModeEnablerStart = 2
|
||||
if GameSettings.UseBasicMouseSensitivity or not AdvancedEnabled then
|
||||
MouseModeEnablerStart = 1
|
||||
end
|
||||
|
||||
-- auto-detect mouse invert
|
||||
local MouseInvertStart = 1
|
||||
if GameSettings.MouseSensitivityFirstPerson.y < 0 then
|
||||
MouseInvertStart = 2
|
||||
end
|
||||
|
||||
if AdvancedEnabled then
|
||||
this.MouseModeFrame,
|
||||
this.MouseModeLabel,
|
||||
this.MouseModeEnabler = utility:AddNewRow(this, "Mouse Sensitivity Mode", "Selector", {"Basic", "Advanced"}, MouseModeEnablerStart)
|
||||
end
|
||||
|
||||
------------------ Basic Mouse Sensitivity Slider ------------------
|
||||
-- basic quantized sensitivity with a weird number of settings.
|
||||
local SliderLabel = "Basic Mouse Sensitivity"
|
||||
if not AdvancedEnabled then
|
||||
SliderLabel = "Mouse Sensitivity"
|
||||
end
|
||||
this.MouseSensitivityFrame,
|
||||
this.MouseSensitivityLabel,
|
||||
this.MouseSensitivitySlider = utility:AddNewRow(this, SliderLabel, "Slider", MouseSteps, startMouseLevel)
|
||||
this.MouseSensitivitySlider:SetMinStep(1)
|
||||
|
||||
this.MouseSensitivitySlider.ValueChanged:connect(function(newValue)
|
||||
GameSettings.MouseSensitivity = translateGuiMouseSensitivityToEngine(newValue)
|
||||
end)
|
||||
|
||||
------------------ 3D Sensitivity ------------------
|
||||
-- affects both first and third person.
|
||||
if AdvancedEnabled then
|
||||
local MouseAdvancedStart = tostring(GameSettings.MouseSensitivityFirstPerson.y)
|
||||
this.MouseAdvancedFrame,
|
||||
this.MouseAdvancedLabel,
|
||||
this.MouseAdvancedEntry = utility:AddNewRow(this, "Advanced Mouse Sensitivity", "TextEntry", 1.0, 1.0, MouseAdvancedStart)
|
||||
|
||||
this.MouseAdvancedEntry.ValueChanged:connect(function(newValueText)
|
||||
local currentFirstSensitivity = GameSettings.MouseSensitivityFirstPerson
|
||||
local currentThirdSensitivity = GameSettings.MouseSensitivityThirdPerson
|
||||
|
||||
local newValue = tonumber(newValueText)
|
||||
if not newValue then
|
||||
this.MouseAdvancedEntry:SetValue(string.format("%.3f",currentFirstSensitivity.x))
|
||||
return
|
||||
end
|
||||
|
||||
-- inverted mouse will be handled later
|
||||
if newValue < 0.0 then
|
||||
newValue = -newValue
|
||||
end
|
||||
|
||||
-- * assume a minimum that allows a 16000 dpi mouse a full 800mm travel for 360deg
|
||||
-- ~0.0029: min of 0.001 seems ok.
|
||||
-- * assume a max that allows a 400 dpi mouse a 360deg travel in 10mm
|
||||
-- ~9.2: max of 10 seems ok, but users will want to have a bit of fun with crazy settings.
|
||||
if newValue > 100.0 then
|
||||
newValue = 100.0
|
||||
elseif newValue < 0.001 then
|
||||
newValue = 0.001
|
||||
end
|
||||
|
||||
-- try to keep ratios the same, even though they aren't exposed to the GUI
|
||||
local firstPersonX = newValue
|
||||
local firstPersonY = newValue * (currentFirstSensitivity.y / currentFirstSensitivity.x)
|
||||
local thirdPersonX = newValue * (currentFirstSensitivity.x / currentThirdSensitivity.x)
|
||||
local thirdPersonY = thirdPersonX * (currentThirdSensitivity.y / currentThirdSensitivity.x)
|
||||
|
||||
-- handle invert
|
||||
if currentFirstSensitivity.y < 0.0 then
|
||||
firstPersonY = -firstPersonY
|
||||
end
|
||||
if currentThirdSensitivity.y < 0.0 then
|
||||
thirdPersonY = -thirdPersonY
|
||||
end
|
||||
|
||||
GameSettings.MouseSensitivityFirstPerson = Vector2.new(firstPersonX, firstPersonY)
|
||||
GameSettings.MouseSensitivityThirdPerson = Vector2.new(thirdPersonX, thirdPersonY)
|
||||
this.MouseAdvancedEntry:SetValue(string.format("%.3f",firstPersonX))
|
||||
|
||||
end)
|
||||
end
|
||||
|
||||
------------------ Mouse Invert ------------------
|
||||
-- This is a common setting in games, even if it is rare
|
||||
if AdvancedEnabled then
|
||||
this.MouseInvertFrame,
|
||||
this.MouseInvertLabel,
|
||||
this.MouseInvertEnabler = utility:AddNewRow(this, "Advanced Mouse Invert", "Selector", {"Normal", "Inverted"}, MouseInvertStart)
|
||||
|
||||
this.MouseInvertEnabler.IndexChanged:connect(function(newIndex)
|
||||
local currentFirstSensitivity = GameSettings.MouseSensitivityFirstPerson
|
||||
local currentThirdSensitivity = GameSettings.MouseSensitivityThirdPerson
|
||||
|
||||
if newIndex == 1 then
|
||||
if currentFirstSensitivity.y < 0.0 then
|
||||
currentFirstSensitivity = Vector2.new(currentFirstSensitivity.x, -currentFirstSensitivity.y)
|
||||
currentThirdSensitivity = Vector2.new(currentThirdSensitivity.x, -currentThirdSensitivity.y)
|
||||
end
|
||||
elseif newIndex == 2 then
|
||||
if currentFirstSensitivity.y > 0.0 then
|
||||
currentFirstSensitivity = Vector2.new(currentFirstSensitivity.x, -currentFirstSensitivity.y)
|
||||
currentThirdSensitivity = Vector2.new(currentThirdSensitivity.x, -currentThirdSensitivity.y)
|
||||
end
|
||||
end
|
||||
GameSettings.MouseSensitivityFirstPerson = currentFirstSensitivity
|
||||
GameSettings.MouseSensitivityThirdPerson = currentThirdSensitivity
|
||||
end)
|
||||
end
|
||||
|
||||
------------------ Init ------------------
|
||||
if AdvancedEnabled then
|
||||
local function setMouseModeToBasic()
|
||||
this.MouseSensitivitySlider:SetZIndex(2)
|
||||
this.MouseSensitivityLabel.ZIndex = 2
|
||||
this.MouseSensitivitySlider:SetInteractable(true)
|
||||
this.MouseSensitivitySlider:SetValue(translateEngineMouseSensitivityToGui(GameSettings.MouseSensitivity))
|
||||
|
||||
this.MouseAdvancedLabel.ZIndex = 1
|
||||
this.MouseAdvancedEntry:SetInteractable(false)
|
||||
|
||||
this.MouseInvertLabel.ZIndex = 1
|
||||
this.MouseInvertEnabler:SetInteractable(false)
|
||||
|
||||
end
|
||||
local function setMouseModeToAdvanced()
|
||||
this.MouseSensitivitySlider:SetZIndex(1)
|
||||
this.MouseSensitivityLabel.ZIndex = 1
|
||||
this.MouseSensitivitySlider:SetInteractable(false)
|
||||
|
||||
this.MouseAdvancedLabel.ZIndex = 2
|
||||
this.MouseAdvancedEntry:SetInteractable(true)
|
||||
local MouseSensitivity3d = GameSettings.MouseSensitivityFirstPerson
|
||||
this.MouseAdvancedEntry:SetValue(tostring(MouseSensitivity3d.x));
|
||||
|
||||
this.MouseInvertLabel.ZIndex = 2
|
||||
this.MouseInvertEnabler:SetInteractable(true)
|
||||
|
||||
end
|
||||
|
||||
this.MouseModeEnabler.IndexChanged:connect(function(newIndex)
|
||||
if newIndex == 1 then
|
||||
GameSettings.UseBasicMouseSensitivity = true
|
||||
setMouseModeToBasic()
|
||||
elseif newIndex == 2 then
|
||||
GameSettings.UseBasicMouseSensitivity = false
|
||||
setMouseModeToAdvanced()
|
||||
end
|
||||
end)
|
||||
|
||||
if GameSettings.UseBasicMouseSensitivity then
|
||||
setMouseModeToBasic()
|
||||
this.MouseAdvancedEntry:SetValue(tostring(MouseAdvancedStart))
|
||||
else
|
||||
setMouseModeToAdvanced()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function createOverscanOption()
|
||||
local showOverscanScreen = function()
|
||||
|
||||
if not overscanScreen then
|
||||
local createOverscanFunc = require(RobloxGui.Modules.OverscanScreen)
|
||||
overscanScreen = createOverscanFunc(RobloxGui)
|
||||
overscanScreen:SetStyleForInGame()
|
||||
end
|
||||
|
||||
local MenuModule = require(RobloxGui.Modules.Settings.SettingsHub)
|
||||
MenuModule:SetVisibility(false, true)
|
||||
|
||||
local closedCon = nil
|
||||
closedCon = overscanScreen.Closed:connect(function()
|
||||
closedCon:disconnect()
|
||||
pcall(function() PlatformService.BlurIntensity = 0 end)
|
||||
ContextActionService:UnbindCoreAction("RbxStopOverscanMovement")
|
||||
MenuModule:SetVisibility(true, true)
|
||||
end)
|
||||
|
||||
pcall(function() PlatformService.BlurIntensity = 10 end)
|
||||
|
||||
local noOpFunc = function() end
|
||||
ContextActionService:BindCoreAction("RbxStopOverscanMovement", noOpFunc, false,
|
||||
Enum.UserInputType.Gamepad1, Enum.UserInputType.Gamepad2,
|
||||
Enum.UserInputType.Gamepad3, Enum.UserInputType.Gamepad4)
|
||||
|
||||
local ScreenManager = require(RobloxGui.Modules.ScreenManager)
|
||||
ScreenManager:OpenScreen(overscanScreen)
|
||||
|
||||
end
|
||||
|
||||
local adjustButton, adjustText, setButtonRowRef = utility:MakeStyledButton("AdjustButton", "Adjust", UDim2.new(0,300,1,-20), showOverscanScreen, this)
|
||||
adjustText.Font = Enum.Font.SourceSans
|
||||
adjustButton.Position = UDim2.new(1,-400,0,12)
|
||||
|
||||
local row = utility:AddNewRowObject(this, "Safe Zone", adjustButton)
|
||||
setButtonRowRef(row)
|
||||
end
|
||||
|
||||
createCameraModeOptions(not isTenFootInterface and
|
||||
(UserInputService.TouchEnabled or UserInputService.MouseEnabled or UserInputService.KeyboardEnabled))
|
||||
|
||||
if UserInputService.MouseEnabled then
|
||||
createMouseOptions()
|
||||
end
|
||||
|
||||
createVolumeOptions()
|
||||
|
||||
if platform == Enum.Platform.Windows or platform == Enum.Platform.UWP or platform == Enum.Platform.OSX then
|
||||
createGraphicsOptions()
|
||||
end
|
||||
|
||||
if isTenFootInterface then
|
||||
createOverscanOption()
|
||||
end
|
||||
|
||||
------ TAB CUSTOMIZATION -------
|
||||
this.TabHeader.Name = "GameSettingsTab"
|
||||
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/GameSettingsTab.png"
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,34,0,34)
|
||||
this.TabHeader.Icon.Position = UDim2.new(this.TabHeader.Icon.Position.X.Scale,this.TabHeader.Icon.Position.X.Offset,0.5,-17)
|
||||
this.TabHeader.Size = UDim2.new(0,125,1,0)
|
||||
elseif isTenFootInterface then
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/GameSettingsTab@2x.png"
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,90,0,90)
|
||||
this.TabHeader.Icon.Position = UDim2.new(0,0,0.5,-43)
|
||||
this.TabHeader.Size = UDim2.new(0,280,1,0)
|
||||
else
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,45,0,45)
|
||||
this.TabHeader.Icon.Position = UDim2.new(0,15,0.5,-22)
|
||||
end
|
||||
|
||||
|
||||
this.TabHeader.Icon.Title.Text = "Settings"
|
||||
|
||||
------ PAGE CUSTOMIZATION -------
|
||||
this.Page.ZIndex = 5
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
----------- Page Instantiation --------------
|
||||
|
||||
PageInstance = Initialize()
|
||||
|
||||
return PageInstance
|
||||
@@ -0,0 +1,489 @@
|
||||
--[[
|
||||
Filename: Help.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.0
|
||||
Description: Takes care of the help page in Settings Menu
|
||||
--]]
|
||||
-------------- CONSTANTS --------------
|
||||
local KEYBOARD_MOUSE_TAG = "KeyboardMouse"
|
||||
local TOUCH_TAG = "Touch"
|
||||
local GAMEPAD_TAG = "Gamepad"
|
||||
local PC_TABLE_SPACING = 4
|
||||
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
----------- UTILITIES --------------
|
||||
local utility = require(RobloxGui.Modules.Settings.Utility)
|
||||
|
||||
------------ Variables -------------------
|
||||
local PageInstance = nil
|
||||
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
|
||||
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
|
||||
|
||||
----------- CLASS DECLARATION --------------
|
||||
|
||||
local function Initialize()
|
||||
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
|
||||
local this = settingsPageFactory:CreateNewPage()
|
||||
this.HelpPages = {}
|
||||
|
||||
-- TODO: Change dev console script to parent this to somewhere other than an engine created gui
|
||||
local ControlFrame = RobloxGui:WaitForChild('ControlFrame')
|
||||
local ToggleDevConsoleBindableFunc = ControlFrame:WaitForChild('ToggleDevConsole')
|
||||
local lastInputType = nil
|
||||
|
||||
function this:GetCurrentInputType()
|
||||
if lastInputType == nil then -- we don't know what controls the user has, just use reasonable defaults
|
||||
local platform = UserInputService:GetPlatform()
|
||||
if platform == Enum.Platform.XBoxOne or platform == Enum.Platform.WiiU then
|
||||
|
||||
return GAMEPAD_TAG
|
||||
elseif platform == Enum.Platform.Windows or platform == Enum.Platform.OSX then
|
||||
return KEYBOARD_MOUSE_TAG
|
||||
else
|
||||
return TOUCH_TAG
|
||||
end
|
||||
end
|
||||
|
||||
if lastInputType == Enum.UserInputType.Keyboard or lastInputType == Enum.UserInputType.MouseMovement or
|
||||
lastInputType == Enum.UserInputType.MouseButton1 or lastInputType == Enum.UserInputType.MouseButton2 or
|
||||
lastInputType == Enum.UserInputType.MouseButton3 or lastInputType == Enum.UserInputType.MouseWheel then
|
||||
return KEYBOARD_MOUSE_TAG
|
||||
elseif lastInputType == Enum.UserInputType.Touch then
|
||||
return TOUCH_TAG
|
||||
elseif lastInputType == Enum.UserInputType.Gamepad1 or lastInputType == Enum.UserInputType.Gamepad2 or
|
||||
inputType == Enum.UserInputType.Gamepad3 or lastInputType == Enum.UserInputType.Gamepad4 then
|
||||
return GAMEPAD_TAG
|
||||
end
|
||||
|
||||
return KEYBOARD_MOUSE_TAG
|
||||
end
|
||||
|
||||
|
||||
local function createPCHelp(parentFrame)
|
||||
local function createPCGroup(title, actionInputBindings)
|
||||
local textIndent = 9
|
||||
|
||||
local pcGroupFrame = utility:Create'Frame'
|
||||
{
|
||||
Size = UDim2.new(1/3,-PC_TABLE_SPACING,1,0),
|
||||
BackgroundTransparency = 1,
|
||||
Name = "PCGroupFrame" .. tostring(title)
|
||||
};
|
||||
local pcGroupTitle = utility:Create'TextLabel'
|
||||
{
|
||||
Position = UDim2.new(0,textIndent,0,0),
|
||||
Size = UDim2.new(1,-textIndent,0,30),
|
||||
BackgroundTransparency = 1,
|
||||
Text = title,
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
FontSize = Enum.FontSize.Size18,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Name = "PCGroupTitle" .. tostring(title),
|
||||
ZIndex = 2,
|
||||
Parent = pcGroupFrame
|
||||
};
|
||||
|
||||
local count = 0
|
||||
local frameHeight = 42
|
||||
local spacing = 2
|
||||
local offset = pcGroupTitle.Size.Y.Offset
|
||||
for i = 1, #actionInputBindings do
|
||||
for actionName, inputName in pairs(actionInputBindings[i]) do
|
||||
local actionInputFrame = utility:Create'Frame'
|
||||
{
|
||||
Size = UDim2.new(1,0,0,frameHeight),
|
||||
Position = UDim2.new(0,0,0, offset + ((frameHeight + spacing) * count)),
|
||||
BackgroundTransparency = 0.65,
|
||||
BorderSizePixel = 0,
|
||||
ZIndex = 2,
|
||||
Name = "ActionInputBinding" .. tostring(actionName),
|
||||
Parent = pcGroupFrame
|
||||
};
|
||||
|
||||
local nameLabel = utility:Create'TextLabel'
|
||||
{
|
||||
Size = UDim2.new(0.4,-textIndent,0,frameHeight),
|
||||
Position = UDim2.new(0,textIndent,0,0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = actionName,
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
FontSize = Enum.FontSize.Size18,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Name = actionName .. "Label",
|
||||
ZIndex = 2,
|
||||
Parent = actionInputFrame
|
||||
};
|
||||
|
||||
local inputLabel = utility:Create'TextLabel'
|
||||
{
|
||||
Size = UDim2.new(0.6,0,0,frameHeight),
|
||||
Position = UDim2.new(0.5,-4,0,0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = inputName,
|
||||
Font = Enum.Font.SourceSans,
|
||||
FontSize = Enum.FontSize.Size18,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Name = inputName .. "Label",
|
||||
ZIndex = 2,
|
||||
Parent = actionInputFrame
|
||||
};
|
||||
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
pcGroupFrame.Size = UDim2.new(pcGroupFrame.Size.X.Scale,pcGroupFrame.Size.X.Offset,
|
||||
0, offset + ((frameHeight + spacing) * count))
|
||||
|
||||
return pcGroupFrame
|
||||
end
|
||||
|
||||
local rowOffset = 50
|
||||
local isOSX = UserInputService:GetPlatform() == Enum.Platform.OSX
|
||||
|
||||
local charMoveFrame = createPCGroup( "Character Movement", {[1] = {["Move Forward"] = "W/Up Arrow"},
|
||||
[2] = {["Move Backward"] = "S/Down Arrow"},
|
||||
[3] = {["Move Left"] = "A/Left Arrow"},
|
||||
[4] = {["Move Right"] = "D/Right Arrow"},
|
||||
[5] = {["Jump"] = "Space"}} )
|
||||
charMoveFrame.Parent = parentFrame
|
||||
|
||||
local accessoriesFrame = createPCGroup("Accessories", { [1] = {["Equip Tools"] = "1,2,3..."},
|
||||
[2] = {["Unequip Tools"] = "1,2,3..."},
|
||||
[3] = {["Drop Tool"] = "Backspace"},
|
||||
[4] = {["Use Tool"] = "Left Mouse Button"},
|
||||
[5] = {["Drop Hats"] = "+"} })
|
||||
accessoriesFrame.Position = UDim2.new(1/3,PC_TABLE_SPACING,0,0)
|
||||
accessoriesFrame.Parent = parentFrame
|
||||
|
||||
local miscFrame = nil
|
||||
local hideHudSuccess, hideHudFlagValue = pcall(function() return settings():GetFFlag("AllowHideHudShortcut") end)
|
||||
if (hideHudSuccess and hideHudFlagValue) then
|
||||
miscFrame = createPCGroup("Misc", { [1] = {["Screenshot"] = "Print Screen"},
|
||||
[2] = {["Record Video"] = isOSX and "F12/fn + F12" or "F12"},
|
||||
[3] = {["Hide HUD"] = isOSX and "F7/fn + F7" or "F7"},
|
||||
[4] = {["Dev Console"] = isOSX and "F9/fn + F9" or "F9"},
|
||||
[5] = {["Mouselock"] = "Shift"},
|
||||
[6] = {["Graphics Level"] = isOSX and "F10/fn + F10" or "F10"},
|
||||
[7] = {["Fullscreen"] = isOSX and "F11/fn + F11" or "F11"} })
|
||||
else
|
||||
miscFrame = createPCGroup("Misc", { [1] = {["Screenshot"] = "Print Screen"},
|
||||
[2] = {["Record Video"] = isOSX and "F12/fn + F12" or "F12"},
|
||||
[3] = {["Dev Console"] = isOSX and "F9/fn + F9" or "F9"},
|
||||
[4] = {["Mouselock"] = "Shift"},
|
||||
[5] = {["Graphics Level"] = isOSX and "F10/fn + F10" or "F10"},
|
||||
[6] = {["Fullscreen"] = isOSX and "F11/fn + F11" or "F11"} })
|
||||
end
|
||||
miscFrame.Position = UDim2.new(2/3,PC_TABLE_SPACING * 2,0,0)
|
||||
miscFrame.Parent = parentFrame
|
||||
|
||||
local camFrame = createPCGroup("Camera Movement", { [1] = {["Rotate"] = "Right Mouse Button"},
|
||||
[2] = {["Zoom In/Out"] = "Mouse Wheel"},
|
||||
[3] = {["Zoom In"] = "I"},
|
||||
[4] = {["Zoom Out"] = "O"} })
|
||||
camFrame.Position = UDim2.new(0,0,charMoveFrame.Size.Y.Scale,charMoveFrame.Size.Y.Offset + rowOffset)
|
||||
camFrame.Parent = parentFrame
|
||||
|
||||
local menuFrame = createPCGroup("Menu Items", { [1] = {["ROBLOX Menu"] = "ESC"},
|
||||
[2] = {["Backpack"] = "~"},
|
||||
[3] = {["Playerlist"] = "TAB"},
|
||||
[4] = {["Chat"] = "/"} })
|
||||
menuFrame.Position = UDim2.new(1/3,PC_TABLE_SPACING,charMoveFrame.Size.Y.Scale,charMoveFrame.Size.Y.Offset + rowOffset)
|
||||
menuFrame.Parent = parentFrame
|
||||
|
||||
parentFrame.Size = UDim2.new(parentFrame.Size.X.Scale, parentFrame.Size.X.Offset, 0,
|
||||
menuFrame.Size.Y.Offset + menuFrame.Position.Y.Offset)
|
||||
end
|
||||
|
||||
local function createGamepadHelp(parentFrame)
|
||||
local gamepadImage = "rbxasset://textures/ui/Settings/Help/GenericController.png"
|
||||
local imageSize = UDim2.new(0,650,0,239)
|
||||
local imagePosition = UDim2.new(0.5,-imageSize.X.Offset/2,0.5,-imageSize.Y.Offset/2)
|
||||
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne or UserInputService:GetPlatform() == Enum.Platform.XBox360 then
|
||||
gamepadImage = "rbxasset://textures/ui/Settings/Help/XboxController.png"
|
||||
imageSize = UDim2.new(0,1334,0,570)
|
||||
imagePosition = UDim2.new(0.5, (-imageSize.X.Offset/2) - 50, 0.5, -imageSize.Y.Offset/2)
|
||||
elseif UserInputService:GetPlatform() == Enum.Platform.PS4 or UserInputService:GetPlatform() == Enum.Platform.PS3 then
|
||||
gamepadImage = "rbxasset://textures/ui/Settings/Help/PSController.png"
|
||||
end
|
||||
|
||||
local gamepadImageLabel = utility:Create'ImageLabel'
|
||||
{
|
||||
Name = "GamepadImage",
|
||||
Size = imageSize,
|
||||
Position = imagePosition,
|
||||
Image = gamepadImage,
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 2,
|
||||
Parent = parentFrame
|
||||
};
|
||||
parentFrame.Size = UDim2.new(parentFrame.Size.X.Scale, parentFrame.Size.X.Offset, 0, gamepadImageLabel.Size.Y.Offset + 100)
|
||||
|
||||
local gamepadFontSize = isTenFootInterface and Enum.FontSize.Size36 or Enum.FontSize.Size24
|
||||
local function createGamepadLabel(text, position, size)
|
||||
local nameLabel = utility:Create'TextLabel'
|
||||
{
|
||||
Position = position,
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
Text = text,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
FontSize = gamepadFontSize,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Name = text .. "Label",
|
||||
ZIndex = 2,
|
||||
Parent = gamepadImageLabel
|
||||
};
|
||||
end
|
||||
|
||||
local textVerticalSize = (gamepadFontSize == Enum.FontSize.Size36) and 36 or 24
|
||||
|
||||
if gamepadImage == "rbxasset://textures/ui/Settings/Help/XboxController.png" then
|
||||
createGamepadLabel("Switch Tool", UDim2.new(0,50,0,-textVerticalSize/2), UDim2.new(0,100,0,textVerticalSize))
|
||||
createGamepadLabel("Game Menu Toggle", UDim2.new(0,-38,0.15,-textVerticalSize/2), UDim2.new(0,164,0,textVerticalSize))
|
||||
createGamepadLabel("Move", UDim2.new(0,-80,0.31,-textVerticalSize/2), UDim2.new(0,46,0,textVerticalSize))
|
||||
createGamepadLabel("Menu Navigation", UDim2.new(0,-50,0.46,-textVerticalSize/2), UDim2.new(0,164,0,textVerticalSize))
|
||||
createGamepadLabel("Use Tool", UDim2.new(0.96,0,0,-textVerticalSize/2), UDim2.new(0,73,0,textVerticalSize))
|
||||
createGamepadLabel("ROBLOX Menu", UDim2.new(0.96,0,0.15,-textVerticalSize/2), UDim2.new(0,122,0,textVerticalSize))
|
||||
createGamepadLabel("Back", UDim2.new(0.96,0,0.31,-textVerticalSize/2), UDim2.new(0,43,0,textVerticalSize))
|
||||
createGamepadLabel("Jump", UDim2.new(0.96,0,0.46,-textVerticalSize/2), UDim2.new(0,49,0,textVerticalSize))
|
||||
createGamepadLabel("Rotate Camera", UDim2.new(1,0,0.62,-textVerticalSize/2), UDim2.new(0,132,0,textVerticalSize))
|
||||
createGamepadLabel("Camera Zoom", UDim2.new(1,0,0.77,-textVerticalSize/2), UDim2.new(0,122,0,textVerticalSize))
|
||||
else
|
||||
createGamepadLabel("Switch Tool", UDim2.new(-0.01,0,0,-textVerticalSize/2), UDim2.new(0,100,0,textVerticalSize))
|
||||
createGamepadLabel("Game Menu Toggle", UDim2.new(-0.11,0,0.15,-textVerticalSize/2), UDim2.new(0,164,0,textVerticalSize))
|
||||
createGamepadLabel("Move", UDim2.new(-0.08,0,0.31,-textVerticalSize/2), UDim2.new(0,46,0,textVerticalSize))
|
||||
createGamepadLabel("Menu Navigation", UDim2.new(-0.125,0,0.46,-textVerticalSize/2), UDim2.new(0,164,0,textVerticalSize))
|
||||
createGamepadLabel("Use Tool", UDim2.new(0.96,0,0,-textVerticalSize/2), UDim2.new(0,73,0,textVerticalSize))
|
||||
createGamepadLabel("ROBLOX Menu", UDim2.new(0.9,0,0.15,-textVerticalSize/2), UDim2.new(0,122,0,textVerticalSize))
|
||||
createGamepadLabel("Back", UDim2.new(1.01,0,0.31,-textVerticalSize/2), UDim2.new(0,43,0,textVerticalSize))
|
||||
createGamepadLabel("Jump", UDim2.new(0.91,0,0.46,-textVerticalSize/2), UDim2.new(0,49,0,textVerticalSize))
|
||||
createGamepadLabel("Rotate Camera", UDim2.new(0.91,0,0.62,-textVerticalSize/2), UDim2.new(0,132,0,textVerticalSize))
|
||||
createGamepadLabel("Camera Zoom", UDim2.new(0.91,0,0.77,-textVerticalSize/2), UDim2.new(0,122,0,textVerticalSize))
|
||||
end
|
||||
|
||||
|
||||
-- todo: turn on dev console button when dev console is ready
|
||||
--[[local openDevConsoleFunc = function()
|
||||
this.HubRef:SetVisibility(false)
|
||||
ToggleDevConsoleBindableFunc:Invoke()
|
||||
end
|
||||
local devConsoleButton = utility:MakeStyledButton("ConsoleButton", " Toggle Dev Console", UDim2.new(0,300,0,44), openDevConsoleFunc)
|
||||
devConsoleButton.Size = UDim2.new(devConsoleButton.Size.X.Scale, devConsoleButton.Size.X.Offset, 0, 60)
|
||||
devConsoleButton.Position = UDim2.new(1,-300,1,30)
|
||||
if UserInputService.GamepadEnabled and not UserInputService.TouchEnabled and not UserInputService.MouseEnabled and not UserInputService.KeyboardEnabled then
|
||||
devConsoleButton.ImageTransparency = 1
|
||||
end
|
||||
devConsoleButton.Parent = gamepadImageLabel
|
||||
local aButtonImage = utility:Create'ImageLabel'
|
||||
{
|
||||
Name = "AButtonImage",
|
||||
Size = UDim2.new(0,55,0,55),
|
||||
Position = UDim2.new(0,5,0.5,-28),
|
||||
Image = "rbxasset://textures/ui/Settings/Help/AButtonDark.png",
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 2,
|
||||
Parent = devConsoleButton
|
||||
};
|
||||
|
||||
this:AddRow(nil, nil, devConsoleButton, 340)]]
|
||||
end
|
||||
|
||||
local function createTouchHelp(parentFrame)
|
||||
local smallScreen = utility:IsSmallTouchScreen()
|
||||
local ySize = GuiService:GetScreenResolution().y - 350
|
||||
if smallScreen then
|
||||
ySize = GuiService:GetScreenResolution().y - 100
|
||||
end
|
||||
parentFrame.Size = UDim2.new(1,0,0,ySize)
|
||||
|
||||
local function createTouchLabel(text, position, size, parent)
|
||||
local nameLabel = utility:Create'TextLabel'
|
||||
{
|
||||
Position = position,
|
||||
Size = size,
|
||||
BackgroundTransparency = 1,
|
||||
Text = text,
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
FontSize = Enum.FontSize.Size14,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Name = text .. "Label",
|
||||
ZIndex = 2,
|
||||
Parent = parent
|
||||
};
|
||||
if not smallScreen then
|
||||
nameLabel.FontSize = Enum.FontSize.Size18
|
||||
nameLabel.Size = UDim2.new(nameLabel.Size.X.Scale, nameLabel.Size.X.Offset, nameLabel.Size.Y.Scale, nameLabel.Size.Y.Offset + 4)
|
||||
end
|
||||
local nameBackgroundImage = utility:Create'ImageLabel'
|
||||
{
|
||||
Name = text .. "BackgroundImage",
|
||||
Size = UDim2.new(1,0,1,0),
|
||||
Position = UDim2.new(0,0,0,2),
|
||||
BackgroundTransparency = 1,
|
||||
Image = "rbxasset://textures/ui/Settings/Radial/RadialLabel.png",
|
||||
ScaleType = Enum.ScaleType.Slice,
|
||||
SliceCenter = Rect.new(12,2,65,21),
|
||||
ZIndex = 2,
|
||||
Parent = nameLabel
|
||||
};
|
||||
|
||||
return nameLabel
|
||||
end
|
||||
|
||||
local function createTouchGestureImage(name, image, position, size, parent)
|
||||
local gestureImage = utility:Create'ImageLabel'
|
||||
{
|
||||
Name = name,
|
||||
Size = size,
|
||||
Position = position,
|
||||
BackgroundTransparency = 1,
|
||||
Image = image,
|
||||
ZIndex = 2,
|
||||
Parent = parent
|
||||
};
|
||||
|
||||
return gestureImage
|
||||
end
|
||||
|
||||
local xSizeOffset = 30
|
||||
local ySize = 25
|
||||
if smallScreen then xSizeOffset = 0 end
|
||||
|
||||
local moveLabel = createTouchLabel("Move", UDim2.new(0.06,0,0.58,0), UDim2.new(0,77 + xSizeOffset,0,ySize), parentFrame)
|
||||
if not smallScreen then moveLabel.Position = UDim2.new(-0.03,0,0.7,0) end
|
||||
local jumpLabel = createTouchLabel("Jump", UDim2.new(0.8,0,0.58,0), UDim2.new(0,77 + xSizeOffset,0,ySize), parentFrame)
|
||||
if not smallScreen then jumpLabel.Position = UDim2.new(0.85,0,0.7,0) end
|
||||
local equipLabel = createTouchLabel("Equip/Unequip Tools", UDim2.new(0.5,-60,0.64,0), UDim2.new(0,120 + xSizeOffset,0,ySize), parentFrame)
|
||||
if not smallScreen then equipLabel.Position = UDim2.new(0.5,-60,0.95,0) end
|
||||
|
||||
local zoomLabel = createTouchLabel("Zoom In/Out", UDim2.new(0.15,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
|
||||
createTouchGestureImage("ZoomImage", "rbxasset://textures/ui/Settings/Help/ZoomGesture.png", UDim2.new(0.5,-26,1,3), UDim2.new(0,53,0,59), zoomLabel)
|
||||
local rotateLabel = createTouchLabel("Rotate Camera", UDim2.new(0.5,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
|
||||
createTouchGestureImage("RotateImage", "rbxasset://textures/ui/Settings/Help/RotateCameraGesture.png", UDim2.new(0.5,-32,1,3), UDim2.new(0,65,0,48), rotateLabel)
|
||||
local useToolLabel = createTouchLabel("Use Tool", UDim2.new(0.85,-60,0.02,0), UDim2.new(0,120,0,ySize), parentFrame)
|
||||
createTouchGestureImage("ToolImage", "rbxasset://textures/ui/Settings/Help/UseToolGesture.png", UDim2.new(0.5,-19,1,3), UDim2.new(0,38,0,52), useToolLabel)
|
||||
|
||||
end
|
||||
|
||||
local function createHelpDisplay(typeOfHelp)
|
||||
local helpFrame = utility:Create'Frame'
|
||||
{
|
||||
Size = UDim2.new(1,0,1,0),
|
||||
BackgroundTransparency = 1,
|
||||
Name = "HelpFrame" .. tostring(typeOfHelp)
|
||||
};
|
||||
|
||||
if typeOfHelp == KEYBOARD_MOUSE_TAG then
|
||||
createPCHelp(helpFrame)
|
||||
elseif typeOfHelp == GAMEPAD_TAG then
|
||||
createGamepadHelp(helpFrame)
|
||||
elseif typeOfHelp == TOUCH_TAG then
|
||||
createTouchHelp(helpFrame)
|
||||
end
|
||||
|
||||
return helpFrame
|
||||
end
|
||||
|
||||
local function displayHelp(currentPage)
|
||||
for i, helpPage in pairs(this.HelpPages) do
|
||||
if helpPage == currentPage then
|
||||
helpPage.Parent = this.Page
|
||||
this.Page.Size = helpPage.Size
|
||||
else
|
||||
helpPage.Parent = nil
|
||||
end
|
||||
end
|
||||
if UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
|
||||
this.HubRef.PageViewClipper.ClipsDescendants = false
|
||||
this.HubRef.PageView.ClipsDescendants = false
|
||||
end
|
||||
end
|
||||
|
||||
local function switchToHelp(typeOfHelp)
|
||||
local helpPage = this.HelpPages[typeOfHelp]
|
||||
if helpPage then
|
||||
displayHelp(helpPage)
|
||||
else
|
||||
this.HelpPages[typeOfHelp] = createHelpDisplay(typeOfHelp)
|
||||
switchToHelp(typeOfHelp)
|
||||
end
|
||||
end
|
||||
|
||||
local function showTypeOfHelp()
|
||||
switchToHelp(this:GetCurrentInputType())
|
||||
end
|
||||
|
||||
------ TAB CUSTOMIZATION -------
|
||||
this.TabHeader.Name = "HelpTab"
|
||||
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/HelpTab.png"
|
||||
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,33,0,33)
|
||||
this.TabHeader.Icon.Position = UDim2.new(this.TabHeader.Icon.Position.X.Scale,this.TabHeader.Icon.Position.X.Offset,0.5,-16)
|
||||
this.TabHeader.Size = UDim2.new(0,100,1,0)
|
||||
elseif isTenFootInterface then
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/HelpTab@2x.png"
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,90,0,90)
|
||||
this.TabHeader.Icon.Position = UDim2.new(0,0,0.5,-43)
|
||||
this.TabHeader.Size = UDim2.new(0,210,1,0)
|
||||
else
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,44,0,44)
|
||||
this.TabHeader.Icon.Position = UDim2.new(this.TabHeader.Icon.Position.X.Scale,this.TabHeader.Icon.Position.X.Offset,0.5,-22)
|
||||
this.TabHeader.Size = UDim2.new(0,130,1,0)
|
||||
end
|
||||
|
||||
this.TabHeader.Icon.Title.Text = "Help"
|
||||
|
||||
|
||||
------ PAGE CUSTOMIZATION -------
|
||||
this.Page.Name = "Help"
|
||||
|
||||
UserInputService.InputBegan:connect(function(inputObject)
|
||||
local inputType = inputObject.UserInputType
|
||||
if inputType ~= Enum.UserInputType.Focus and inputType ~= Enum.UserInputType.None then
|
||||
lastInputType = inputObject.UserInputType
|
||||
showTypeOfHelp()
|
||||
end
|
||||
end)
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
----------- Public Facing API Additions --------------
|
||||
do
|
||||
PageInstance = Initialize()
|
||||
|
||||
PageInstance.Displayed.Event:connect(function()
|
||||
if PageInstance:GetCurrentInputType() == TOUCH_TAG then
|
||||
if PageInstance.HubRef.BottomButtonFrame and not utility:IsSmallTouchScreen() then
|
||||
PageInstance.HubRef.BottomButtonFrame.Visible = false
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
PageInstance.Hidden.Event:connect(function()
|
||||
PageInstance.HubRef.PageViewClipper.ClipsDescendants = true
|
||||
PageInstance.HubRef.PageView.ClipsDescendants = true
|
||||
|
||||
PageInstance.HubRef:ShowShield()
|
||||
|
||||
if PageInstance:GetCurrentInputType() == TOUCH_TAG then
|
||||
PageInstance.HubRef.BottomButtonFrame.Visible = true
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
return PageInstance
|
||||
@@ -0,0 +1,124 @@
|
||||
--[[
|
||||
Filename: LeaveGame.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.0
|
||||
Description: Takes care of the leave game in Settings Menu
|
||||
--]]
|
||||
|
||||
|
||||
-------------- CONSTANTS -------------
|
||||
local LEAVE_GAME_ACTION = "LeaveGameCancelAction"
|
||||
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
----------- UTILITIES --------------
|
||||
local utility = require(RobloxGui.Modules.Settings.Utility)
|
||||
|
||||
------------ Variables -------------------
|
||||
local PageInstance = nil
|
||||
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
|
||||
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
|
||||
|
||||
|
||||
----------- CLASS DECLARATION --------------
|
||||
|
||||
local function Initialize()
|
||||
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
|
||||
local this = settingsPageFactory:CreateNewPage()
|
||||
|
||||
this.DontLeaveFunc = function(isUsingGamepad)
|
||||
if this.HubRef then
|
||||
this.HubRef:PopMenu(isUsingGamepad, true)
|
||||
end
|
||||
end
|
||||
this.DontLeaveFromHotkey = function(name, state, input)
|
||||
if state == Enum.UserInputState.Begin then
|
||||
local isUsingGamepad = input.UserInputType == Enum.UserInputType.Gamepad1 or input.UserInputType == Enum.UserInputType.Gamepad2
|
||||
or input.UserInputType == Enum.UserInputType.Gamepad3 or input.UserInputType == Enum.UserInputType.Gamepad4
|
||||
|
||||
this.DontLeaveFunc(isUsingGamepad)
|
||||
end
|
||||
end
|
||||
this.DontLeaveFromButton = function(isUsingGamepad)
|
||||
this.DontLeaveFunc(isUsingGamepad)
|
||||
end
|
||||
|
||||
------ TAB CUSTOMIZATION -------
|
||||
this.TabHeader = nil -- no tab for this page
|
||||
|
||||
------ PAGE CUSTOMIZATION -------
|
||||
this.Page.Name = "LeaveGamePage"
|
||||
|
||||
local leaveGameText = utility:Create'TextLabel'
|
||||
{
|
||||
Name = "LeaveGameText",
|
||||
Text = "Are you sure you want to leave the game?",
|
||||
Font = Enum.Font.SourceSansBold,
|
||||
FontSize = Enum.FontSize.Size36,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1,0,0,200),
|
||||
TextWrapped = true,
|
||||
ZIndex = 2,
|
||||
Parent = this.Page
|
||||
};
|
||||
if utility:IsSmallTouchScreen() then
|
||||
leaveGameText.FontSize = Enum.FontSize.Size24
|
||||
leaveGameText.Size = UDim2.new(1,0,0,100)
|
||||
elseif isTenFootInterface then
|
||||
leaveGameText.FontSize = Enum.FontSize.Size48
|
||||
end
|
||||
|
||||
local buttonSpacing = 20
|
||||
local buttonSize = UDim2.new(0, 200, 0, 50)
|
||||
if isTenFootInterface then
|
||||
leaveGameText.Position = UDim2.new(0,0,0,100)
|
||||
buttonSize = UDim2.new(0, 300, 0, 80)
|
||||
end
|
||||
|
||||
this.LeaveGameButton = utility:MakeStyledButton("LeaveGame", "Leave", buttonSize)
|
||||
this.LeaveGameButton.NextSelectionRight = nil
|
||||
this.LeaveGameButton:SetVerb("Exit")
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.LeaveGameButton.Position = UDim2.new(0.5, -buttonSize.X.Offset - buttonSpacing, 1, 0)
|
||||
else
|
||||
this.LeaveGameButton.Position = UDim2.new(0.5, -buttonSize.X.Offset - buttonSpacing, 1, -30)
|
||||
end
|
||||
this.LeaveGameButton.Parent = leaveGameText
|
||||
|
||||
|
||||
------------- Init ----------------------------------
|
||||
|
||||
local dontleaveGameButton = utility:MakeStyledButton("DontLeaveGame", "Don't Leave", buttonSize, this.DontLeaveFromButton)
|
||||
dontleaveGameButton.NextSelectionLeft = nil
|
||||
if utility:IsSmallTouchScreen() then
|
||||
dontleaveGameButton.Position = UDim2.new(0.5, buttonSpacing, 1, 0)
|
||||
else
|
||||
dontleaveGameButton.Position = UDim2.new(0.5, buttonSpacing, 1, -30)
|
||||
end
|
||||
dontleaveGameButton.Parent = leaveGameText
|
||||
|
||||
this.Page.Size = UDim2.new(1,0,0,dontleaveGameButton.AbsolutePosition.Y + dontleaveGameButton.AbsoluteSize.Y)
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
----------- Public Facing API Additions --------------
|
||||
PageInstance = Initialize()
|
||||
|
||||
PageInstance.Displayed.Event:connect(function()
|
||||
GuiService.SelectedCoreObject = PageInstance.LeaveGameButton
|
||||
ContextActionService:BindCoreAction(LEAVE_GAME_ACTION, PageInstance.DontLeaveFromHotkey, false, Enum.KeyCode.ButtonB)
|
||||
end)
|
||||
|
||||
PageInstance.Hidden.Event:connect(function()
|
||||
ContextActionService:UnbindCoreAction(LEAVE_GAME_ACTION)
|
||||
end)
|
||||
|
||||
|
||||
return PageInstance
|
||||
@@ -0,0 +1,170 @@
|
||||
--[[r
|
||||
Filename: Record.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.0
|
||||
Description: Takes care of the Record Tab in Settings Menu
|
||||
--]]
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local Settings = UserSettings()
|
||||
local GameSettings = Settings.GameSettings
|
||||
|
||||
----------- UTILITIES --------------
|
||||
RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface")
|
||||
local utility = require(RobloxGui.Modules.Settings.Utility)
|
||||
local isTenFootInterface = require(RobloxGui.Modules.TenFootInterface):IsEnabled()
|
||||
|
||||
------------ Variables -------------------
|
||||
local PageInstance = nil
|
||||
|
||||
----------- CLASS DECLARATION --------------
|
||||
|
||||
local function Initialize()
|
||||
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
|
||||
local this = settingsPageFactory:CreateNewPage()
|
||||
local isRecordingVideo = false
|
||||
|
||||
local recordingEvent = Instance.new("BindableEvent")
|
||||
recordingEvent.Name = "RecordingEvent"
|
||||
this.RecordingChanged = recordingEvent.Event
|
||||
function this:IsRecording()
|
||||
return isRecordingVideo
|
||||
end
|
||||
|
||||
------ TAB CUSTOMIZATION -------
|
||||
this.TabHeader.Name = "RecordTab"
|
||||
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/RecordTab.png"
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,41,0,40)
|
||||
this.TabHeader.Icon.Position = UDim2.new(0,5,0.5,-20)
|
||||
|
||||
this.TabHeader.Icon.Title.Text = "Record"
|
||||
|
||||
this.TabHeader.Size = UDim2.new(0,130,1,0)
|
||||
|
||||
|
||||
------ PAGE CUSTOMIZATION -------
|
||||
this.Page.Name = "Record"
|
||||
|
||||
local function makeTextLabel(name, text, bold, size, pos, parent)
|
||||
local textLabel = utility:Create'TextLabel'
|
||||
{
|
||||
Name = name,
|
||||
BackgroundTransparency = 1,
|
||||
Text = text,
|
||||
TextWrapped = true,
|
||||
Font = Enum.Font.SourceSans,
|
||||
FontSize = Enum.FontSize.Size24,
|
||||
TextColor3 = Color3.new(1,1,1),
|
||||
Size = size,
|
||||
Position = pos,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
TextYAlignment = Enum.TextYAlignment.Top,
|
||||
ZIndex = 2,
|
||||
Parent = parent
|
||||
};
|
||||
if bold then textLabel.Font = Enum.Font.SourceSansBold end
|
||||
|
||||
return textLabel
|
||||
end
|
||||
|
||||
-- need to override this function from SettingsPageFactory
|
||||
-- DropDown menus require hub to to be set when they are initialized
|
||||
function this:SetHub(newHubRef)
|
||||
this.HubRef = newHubRef
|
||||
|
||||
local recordEnumNames = {}
|
||||
recordEnumNames[1] = "Save To Disk"
|
||||
recordEnumNames[2] = "Upload to YouTube"
|
||||
|
||||
local startSetting = 2
|
||||
if GameSettings.VideoUploadPromptBehavior == Enum.UploadSetting["Never"] then
|
||||
startSetting = 1
|
||||
end
|
||||
|
||||
---------------------------------- SCREENSHOT -------------------------------------
|
||||
local screenshotTitle = makeTextLabel("ScreenshotTitle",
|
||||
"Screenshot",
|
||||
true, UDim2.new(1,0,0,36), UDim2.new(0,10,0.05,0), this.Page)
|
||||
screenshotTitle.FontSize = Enum.FontSize.Size36
|
||||
|
||||
local screenshotBody = makeTextLabel("ScreenshotBody",
|
||||
"By clicking the 'Take Screenshot' button, the menu will close and take a screenshot and save it to your computer.",
|
||||
false, UDim2.new(1,-10,0,70), UDim2.new(0,0,1,0), screenshotTitle)
|
||||
|
||||
local closeSettingsFunc = function()
|
||||
this.HubRef:SetVisibility(false, true)
|
||||
end
|
||||
this.ScreenshotButton = utility:MakeStyledButton("ScreenshotButton", "Take Screenshot", UDim2.new(0,300,0,44), closeSettingsFunc, this)
|
||||
|
||||
this.ScreenshotButton.Position = UDim2.new(0,400,1,0)
|
||||
this.ScreenshotButton.Parent = screenshotBody
|
||||
|
||||
|
||||
---------------------------------- VIDEO -------------------------------------
|
||||
local videoTitle = makeTextLabel("VideoTitle",
|
||||
"Video",
|
||||
true, UDim2.new(1,0,0,36), UDim2.new(0,10,0.5,0), this.Page)
|
||||
videoTitle.FontSize = Enum.FontSize.Size36
|
||||
|
||||
local videoBody = makeTextLabel("VideoBody",
|
||||
"By clicking the 'Record Video' button, the menu will close and start recording your screen.",
|
||||
false, UDim2.new(1,-10,0,70), UDim2.new(0,0,1,0), videoTitle)
|
||||
|
||||
this.VideoSettingsFrame,
|
||||
this.VideoSettingsLabel,
|
||||
this.VideoSettingsMode = utility:AddNewRow(this, "Video Settings", "Selector", recordEnumNames, startSetting, 270)
|
||||
|
||||
this.VideoSettingsMode.IndexChanged:connect(function(newIndex)
|
||||
if newIndex == 1 then
|
||||
GameSettings.VideoUploadPromptBehavior = Enum.UploadSetting.Never
|
||||
elseif newIndex == 2 then
|
||||
GameSettings.VideoUploadPromptBehavior = Enum.UploadSetting.Always
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
local recordButton = utility:MakeStyledButton("RecordButton", "Record Video", UDim2.new(0,300,0,44), closeSettingsFunc, this)
|
||||
|
||||
recordButton.Position = UDim2.new(0,410,1,10)
|
||||
recordButton.Parent = this.VideoSettingsMode.SelectorFrame.Parent
|
||||
recordButton.MouseButton1Click:connect(function()
|
||||
recordingEvent:Fire(not isRecordingVideo)
|
||||
end)
|
||||
|
||||
local gameOptions = settings():FindFirstChild("Game Options")
|
||||
if gameOptions then
|
||||
gameOptions.VideoRecordingChangeRequest:connect(function(recording)
|
||||
isRecordingVideo = recording
|
||||
if recording then
|
||||
recordButton.RecordButtonTextLabel.Text = "Stop Recording"
|
||||
else
|
||||
recordButton.RecordButtonTextLabel.Text = "Record Video"
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
recordButton:SetVerb("RecordToggle")
|
||||
this.ScreenshotButton:SetVerb("Screenshot")
|
||||
|
||||
this.Page.Size = UDim2.new(1,0,0,400)
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
----------- Public Facing API Additions --------------
|
||||
PageInstance = Initialize()
|
||||
|
||||
PageInstance.Displayed.Event:connect(function(switchedFromGamepadInput)
|
||||
if switchedFromGamepadInput then
|
||||
GuiService.SelectedCoreObject = PageInstance.ScreenshotButton
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
return PageInstance
|
||||
@@ -0,0 +1,311 @@
|
||||
--[[
|
||||
Filename: ReportAbuseMenu.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.0
|
||||
Description: Takes care of the report abuse page in Settings Menu
|
||||
--]]
|
||||
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local PlayersService = game:GetService("Players")
|
||||
|
||||
----------- UTILITIES --------------
|
||||
local utility = require(RobloxGui.Modules.Settings.Utility)
|
||||
|
||||
------------ CONSTANTS -------------------
|
||||
local ABUSE_TYPES_PLAYER = {
|
||||
"Swearing",
|
||||
"Inappropriate Username",
|
||||
"Bullying",
|
||||
"Scamming",
|
||||
"Dating",
|
||||
"Cheating/Exploiting",
|
||||
"Personal Question",
|
||||
"Offsite Links",
|
||||
}
|
||||
|
||||
local ABUSE_TYPES_GAME = {
|
||||
"Inappropriate Content",
|
||||
"Bad Model or Script",
|
||||
"Offsite Link",
|
||||
}
|
||||
local DEFAULT_ABUSE_DESC_TEXT = " Short Description (Optional)"
|
||||
if utility:IsSmallTouchScreen() then
|
||||
DEFAULT_ABUSE_DESC_TEXT = " (Optional)"
|
||||
end
|
||||
|
||||
------------ VARIABLES -------------------
|
||||
local PageInstance = nil
|
||||
|
||||
----------- CLASS DECLARATION --------------
|
||||
local function Initialize()
|
||||
local settingsPageFactory = require(RobloxGui.Modules.Settings.SettingsPageFactory)
|
||||
local this = settingsPageFactory:CreateNewPage()
|
||||
|
||||
local playerNames = {}
|
||||
local nameToRbxPlayer = {}
|
||||
|
||||
function this:GetPlayerFromIndex(index)
|
||||
local playerName = playerNames[index]
|
||||
if playerName then
|
||||
return nameToRbxPlayer[nameToRbxPlayer]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
function this:UpdatePlayerDropDown()
|
||||
playerNames = {}
|
||||
nameToRbxPlayer = {}
|
||||
|
||||
local players = PlayersService:GetPlayers()
|
||||
local index = 1
|
||||
for i = 1, #players do
|
||||
local player = players[i]
|
||||
if player ~= PlayersService.LocalPlayer and player.UserId > 0 then
|
||||
playerNames[index] = player.Name
|
||||
nameToRbxPlayer[player.Name] = player
|
||||
index = index + 1
|
||||
end
|
||||
end
|
||||
|
||||
this.WhichPlayerMode:UpdateDropDownList(playerNames)
|
||||
|
||||
if index == 1 then
|
||||
this.GameOrPlayerMode:SetSelectionIndex(1)
|
||||
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_GAME)
|
||||
end
|
||||
|
||||
this.WhichPlayerMode:SetInteractable(index > 1 and this.GameOrPlayerMode.CurrentIndex ~= 1)
|
||||
this.GameOrPlayerMode:SetInteractable(index > 1)
|
||||
end
|
||||
|
||||
------ TAB CUSTOMIZATION -------
|
||||
this.TabHeader.Name = "ReportAbuseTab"
|
||||
|
||||
this.TabHeader.Icon.Image = "rbxasset://textures/ui/Settings/MenuBarIcons/ReportAbuseTab.png"
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,27,0,32)
|
||||
this.TabHeader.Size = UDim2.new(0,120,1,0)
|
||||
else
|
||||
this.TabHeader.Size = UDim2.new(0,150,1,0)
|
||||
this.TabHeader.Icon.Size = UDim2.new(0,36,0,43)
|
||||
end
|
||||
this.TabHeader.Icon.Position = UDim2.new(this.TabHeader.Icon.Position.X.Scale, this.TabHeader.Icon.Position.X.Offset + 10, 0.5,-this.TabHeader.Icon.Size.Y.Offset/2)
|
||||
|
||||
this.TabHeader.Icon.Title.Text = "Report"
|
||||
|
||||
------ PAGE CUSTOMIZATION -------
|
||||
this.Page.Name = "ReportAbusePage"
|
||||
|
||||
-- need to override this function from SettingsPageFactory
|
||||
-- DropDown menus require hub to to be set when they are initialized
|
||||
function this:SetHub(newHubRef)
|
||||
this.HubRef = newHubRef
|
||||
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.GameOrPlayerFrame,
|
||||
this.GameOrPlayerLabel,
|
||||
this.GameOrPlayerMode = utility:AddNewRow(this, "Game or Player?", "Selector", {"Game", "Player"}, 1)
|
||||
else
|
||||
this.GameOrPlayerFrame,
|
||||
this.GameOrPlayerLabel,
|
||||
this.GameOrPlayerMode = utility:AddNewRow(this, "Game or Player?", "Selector", {"Game", "Player"}, 1, 3)
|
||||
end
|
||||
|
||||
this.WhichPlayerFrame,
|
||||
this.WhichPlayerLabel,
|
||||
this.WhichPlayerMode = utility:AddNewRow(this, "Which Player?", "DropDown", {"update me"})
|
||||
this.WhichPlayerMode:SetInteractable(false)
|
||||
this.WhichPlayerLabel.ZIndex = 1
|
||||
|
||||
this.TypeOfAbuseFrame,
|
||||
this.TypeOfAbuseLabel,
|
||||
this.TypeOfAbuseMode = utility:AddNewRow(this, "Type Of Abuse", "DropDown", ABUSE_TYPES_GAME)
|
||||
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.AbuseDescriptionFrame,
|
||||
this.AbuseDescriptionLabel,
|
||||
this.AbuseDescription = utility:AddNewRow(this, DEFAULT_ABUSE_DESC_TEXT, "TextBox", nil, nil)
|
||||
else
|
||||
this.AbuseDescriptionFrame,
|
||||
this.AbuseDescriptionLabel,
|
||||
this.AbuseDescription = utility:AddNewRow(this, DEFAULT_ABUSE_DESC_TEXT, "TextBox", nil, nil, 5)
|
||||
end
|
||||
|
||||
if utility:IsSmallTouchScreen() then
|
||||
this.AbuseDescription.Selection.Size = UDim2.new(0, 290, 0, 30)
|
||||
this.AbuseDescription.Selection.Position = UDim2.new(1,-345,this.AbuseDescription.Selection.Position.Y.Scale, this.AbuseDescription.Selection.Position.Y.Offset)
|
||||
|
||||
this.AbuseDescriptionLabel = this.TypeOfAbuseLabel:clone()
|
||||
this.AbuseDescriptionLabel.Text = "Abuse Description"
|
||||
this.AbuseDescriptionLabel.Position = UDim2.new(this.AbuseDescriptionLabel.Position.X.Scale, this.AbuseDescriptionLabel.Position.X.Offset,
|
||||
0,50)
|
||||
this.AbuseDescriptionLabel.Parent = this.Page
|
||||
end
|
||||
|
||||
local SelectionOverrideObject = utility:Create'ImageLabel'
|
||||
{
|
||||
Image = "",
|
||||
BackgroundTransparency = 1
|
||||
};
|
||||
|
||||
local submitButton, submitText = nil, nil
|
||||
|
||||
local function makeSubmitButtonActive()
|
||||
submitButton.ZIndex = 2
|
||||
submitButton.Selectable = true
|
||||
submitText.ZIndex = 2
|
||||
end
|
||||
|
||||
local function makeSubmitButtonInactive()
|
||||
submitButton.ZIndex = 1
|
||||
submitButton.Selectable = false
|
||||
submitText.ZIndex = 1
|
||||
end
|
||||
|
||||
local function updateAbuseDropDown()
|
||||
this.WhichPlayerMode:ResetSelectionIndex()
|
||||
this.TypeOfAbuseMode:ResetSelectionIndex()
|
||||
|
||||
if this.GameOrPlayerMode.CurrentIndex == 1 then
|
||||
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_GAME)
|
||||
this.WhichPlayerMode:SetInteractable(false)
|
||||
this.WhichPlayerLabel.ZIndex = 1
|
||||
this.GameOrPlayerMode.SelectorFrame.NextSelectionDown = this.TypeOfAbuseMode.DropDownFrame
|
||||
else
|
||||
this.TypeOfAbuseMode:UpdateDropDownList(ABUSE_TYPES_PLAYER)
|
||||
this.WhichPlayerMode:SetInteractable(true)
|
||||
this.WhichPlayerLabel.ZIndex = 2
|
||||
this.GameOrPlayerMode.SelectorFrame.NextSelectionDown = this.WhichPlayerMode.DropDownFrame
|
||||
end
|
||||
makeSubmitButtonInactive()
|
||||
end
|
||||
|
||||
local function cleanupReportAbuseMenu()
|
||||
updateAbuseDropDown()
|
||||
this.AbuseDescription.Selection.Text = DEFAULT_ABUSE_DESC_TEXT
|
||||
this.HubRef:SetVisibility(false, true)
|
||||
end
|
||||
|
||||
local function onReportSubmitted()
|
||||
local abuseReason = nil
|
||||
if this.GameOrPlayerMode.CurrentIndex == 2 then
|
||||
abuseReason = ABUSE_TYPES_PLAYER[this.TypeOfAbuseMode.CurrentIndex]
|
||||
|
||||
local currentAbusingPlayer = this:GetPlayerFromIndex(this.WhichPlayerMode.CurrentIndex)
|
||||
if currentAbusingPlayer and abuseReason then
|
||||
spawn(function()
|
||||
game.Players:ReportAbuse(currentAbusingPlayer, abuseReason, this.AbuseDescription.Selection.Text)
|
||||
end)
|
||||
end
|
||||
else
|
||||
abuseReason = ABUSE_TYPES_GAME[this.TypeOfAbuseMode.CurrentIndex]
|
||||
if abuseReason then
|
||||
spawn(function()
|
||||
game.Players:ReportAbuse(nil, abuseReason, this.AbuseDescription.Selection.Text)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
if abuseReason then
|
||||
local alertText = "Thanks for your report! Our moderators will review the chat logs and evaluate what happened."
|
||||
|
||||
if abuseReason == 'Cheating/Exploiting' then
|
||||
alertText = "Thanks for your report! We've recorded your report for evaluation."
|
||||
elseif abuseReason == 'Inappropriate Username' then
|
||||
alertText = "Thanks for your report! Our moderators will evaluate the username."
|
||||
elseif abuseReason == "Bad Model or Script" or abuseReason == "Inappropriate Content" or abuseReason == "Offsite Link" or abuseReason == "Offsite Links" then
|
||||
alertText = "Thanks for your report! Our moderators will review the place and make a determination."
|
||||
end
|
||||
|
||||
utility:ShowAlert(alertText, "Ok", this.HubRef, cleanupReportAbuseMenu)
|
||||
|
||||
this.LastSelectedObject = nil
|
||||
end
|
||||
end
|
||||
|
||||
submitButton, submitText = utility:MakeStyledButton("SubmitButton", "Submit", UDim2.new(0,198,0,50), onReportSubmitted, this)
|
||||
if utility:IsSmallTouchScreen() then
|
||||
submitButton.Position = UDim2.new(1,-220,1,5)
|
||||
else
|
||||
submitButton.Position = UDim2.new(1,-194,1,5)
|
||||
end
|
||||
submitButton.Selectable = false
|
||||
submitButton.ZIndex = 1
|
||||
submitText.ZIndex = 1
|
||||
submitButton.Parent = this.AbuseDescription.Selection
|
||||
|
||||
local function playerSelectionChanged(newIndex)
|
||||
if newIndex ~= nil and this.TypeOfAbuseMode:GetSelectedIndex() ~= nil then
|
||||
makeSubmitButtonActive()
|
||||
else
|
||||
makeSubmitButtonInactive()
|
||||
end
|
||||
end
|
||||
this.WhichPlayerMode.IndexChanged:connect(playerSelectionChanged)
|
||||
|
||||
local function typeOfAbuseChanged(newIndex)
|
||||
if newIndex ~= nil then
|
||||
if this.GameOrPlayerMode.CurrentIndex == 1 or this.WhichPlayerMode:GetSelectedIndex() ~= nil then
|
||||
makeSubmitButtonActive()
|
||||
else
|
||||
makeSubmitButtonInactive()
|
||||
end
|
||||
else
|
||||
makeSubmitButtonInactive()
|
||||
end
|
||||
end
|
||||
this.TypeOfAbuseMode.IndexChanged:connect(typeOfAbuseChanged)
|
||||
|
||||
this.GameOrPlayerMode.IndexChanged:connect(updateAbuseDropDown)
|
||||
|
||||
this:AddRow(nil, nil, this.AbuseDescription)
|
||||
|
||||
this.Page.Size = UDim2.new(1,0,0,submitButton.AbsolutePosition.Y + submitButton.AbsoluteSize.Y)
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
----------- Public Facing API Additions --------------
|
||||
do
|
||||
PageInstance = Initialize()
|
||||
|
||||
PageInstance.Displayed.Event:connect(function()
|
||||
PageInstance:UpdatePlayerDropDown()
|
||||
end)
|
||||
|
||||
function PageInstance:ReportPlayer(player)
|
||||
if player then
|
||||
local setReportPlayerConnection = nil
|
||||
setReportPlayerConnection = PageInstance.Displayed.Event:connect(function()
|
||||
-- When we change the SelectionIndex of GameOrPlayerMode it waits until the tween is done
|
||||
-- before it fires the IndexChanged signal. The WhichPlayerMode dropdown listens to this signal
|
||||
-- and resets when it is fired. Therefore we need to listen to this signal and set the player we want
|
||||
-- to report the frame after the dropdown is reset
|
||||
local indexChangedConnection = nil
|
||||
indexChangedConnection = PageInstance.GameOrPlayerMode.IndexChanged:connect(function()
|
||||
if indexChangedConnection then
|
||||
indexChangedConnection:disconnect()
|
||||
indexChangedConnection = nil
|
||||
end
|
||||
PageInstance.WhichPlayerMode:SetSelectionByValue(player.Name)
|
||||
end)
|
||||
PageInstance.GameOrPlayerMode:SetSelectionIndex(2)
|
||||
|
||||
if setReportPlayerConnection then
|
||||
setReportPlayerConnection:disconnect()
|
||||
setReportPlayerConnection = nil
|
||||
end
|
||||
end)
|
||||
PageInstance.HubRef:SetVisibility(true, false, PageInstance)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return PageInstance
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,345 @@
|
||||
--[[
|
||||
Filename: TenFootInterface.lua
|
||||
Written by: jeditkacheff
|
||||
Version 1.0
|
||||
Description: Setups up some special UI for ROBLOX TV gaming
|
||||
--]]
|
||||
-------------- CONSTANTS --------------
|
||||
local HEALTH_GREEN_COLOR = Color3.new(27/255, 252/255, 107/255)
|
||||
local DISPLAY_POS_INIT_INSET = 0
|
||||
local DISPLAY_ITEM_OFFSET = 4
|
||||
local FORCE_TEN_FOOT_INTERFACE = false
|
||||
|
||||
-------------- SERVICES --------------
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
------------------ VARIABLES --------------------
|
||||
local tenFootInterfaceEnabled = false
|
||||
do
|
||||
local platform = UserInputService:GetPlatform()
|
||||
|
||||
tenFootInterfaceEnabled = (platform == Enum.Platform.XBoxOne or platform == Enum.Platform.WiiU or platform == Enum.Platform.PS4 or
|
||||
platform == Enum.Platform.AndroidTV or platform == Enum.Platform.XBox360 or platform == Enum.Platform.PS3 or
|
||||
platform == Enum.Platform.Ouya or platform == Enum.Platform.SteamOS)
|
||||
end
|
||||
|
||||
if FORCE_TEN_FOOT_INTERFACE then
|
||||
tenFootInterfaceEnabled = true
|
||||
end
|
||||
|
||||
local Util = {}
|
||||
do
|
||||
function Util.Create(instanceType)
|
||||
return function(data)
|
||||
local obj = Instance.new(instanceType)
|
||||
for k, v in pairs(data) do
|
||||
if type(k) == 'number' then
|
||||
v.Parent = obj
|
||||
else
|
||||
obj[k] = v
|
||||
end
|
||||
end
|
||||
return obj
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function CreateModule()
|
||||
local this = {}
|
||||
local nextObjectDisplayYPos = DISPLAY_POS_INIT_INSET
|
||||
local displayStack = {}
|
||||
|
||||
-- setup base gui
|
||||
local function createContainer()
|
||||
if not this.Container then
|
||||
this.Container = Util.Create'ImageButton'
|
||||
{
|
||||
Name = "TopRightContainer";
|
||||
Size = UDim2.new(0, 350, 0, 100);
|
||||
Position = UDim2.new(1,-360,0,10);
|
||||
AutoButtonColor = false;
|
||||
Image = "";
|
||||
Active = false;
|
||||
BackgroundTransparency = 1;
|
||||
Parent = RobloxGui;
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
function removeFromDisplayStack(displayObject)
|
||||
local moveUpFromHere = nil
|
||||
|
||||
for i = 1, #displayStack do
|
||||
if displayStack[i] == displayObject then
|
||||
moveUpFromHere = i + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local prevObject = displayObject
|
||||
for i = moveUpFromHere, #displayStack do
|
||||
local objectToMoveUp = displayStack[i]
|
||||
objectToMoveUp.Position = UDim2.new(objectToMoveUp.Position.X.Scale, objectToMoveUp.Position.X.Offset,
|
||||
objectToMoveUp.Position.Y.Scale, prevObject.AbsolutePosition.Y)
|
||||
prevObject = objectToMoveUp
|
||||
end
|
||||
end
|
||||
|
||||
function addBackToDisplayStack(displayObject)
|
||||
for i = 1, #displayStack do
|
||||
if displayStack[i] == displayObject then
|
||||
moveDownFromHere = i + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local prevObject = displayObject
|
||||
for i = moveDownFromHere, #displayStack do
|
||||
local objectToMoveDown = displayStack[i]
|
||||
local nextDisplayPos = prevObject.AbsolutePosition.Y + prevObject.AbsoluteSize.Y + DISPLAY_ITEM_OFFSET
|
||||
objectToMoveDown.Position = UDim2.new(objectToMoveDown.Position.X.Scale, objectToMoveDown.Position.X.Offset,
|
||||
objectToMoveDown.Position.Y.Scale, nextDisplayPos)
|
||||
prevObject = objectToMoveDown
|
||||
end
|
||||
end
|
||||
|
||||
function addToDisplayStack(displayObject)
|
||||
local lastDisplayed = nil
|
||||
if #displayStack > 0 then
|
||||
lastDisplayed = displayStack[#displayStack]
|
||||
end
|
||||
displayStack[#displayStack + 1] = displayObject
|
||||
|
||||
local nextDisplayPos = DISPLAY_POS_INIT_INSET
|
||||
if lastDisplayed then
|
||||
nextDisplayPos = lastDisplayed.AbsolutePosition.Y + lastDisplayed.AbsoluteSize.Y + DISPLAY_ITEM_OFFSET
|
||||
end
|
||||
|
||||
displayObject.Position = UDim2.new(displayObject.Position.X.Scale, displayObject.Position.X.Offset,
|
||||
displayObject.Position.Y.Scale, nextDisplayPos)
|
||||
|
||||
createContainer()
|
||||
displayObject.Parent = this.Container
|
||||
|
||||
displayObject.Changed:connect(function(prop)
|
||||
if prop == "Visible" then
|
||||
if not displayObject.Visible then
|
||||
removeFromDisplayStack(displayObject)
|
||||
else
|
||||
addBackToDisplayStack(displayObject)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function this:CreateHealthBar()
|
||||
this.HealthContainer = Util.Create'Frame'{
|
||||
Name = "HealthContainer";
|
||||
Size = UDim2.new(1, -86, 0, 50);
|
||||
Position = UDim2.new(0, 92, 0, 0);
|
||||
BorderSizePixel = 0;
|
||||
BackgroundColor3 = Color3.new(0,0,0);
|
||||
BackgroundTransparency = 0.5;
|
||||
};
|
||||
|
||||
local healthFillHolder = Util.Create'Frame'{
|
||||
Name = "HealthFillHolder";
|
||||
Size = UDim2.new(1, -10, 1, -10);
|
||||
Position = UDim2.new(0, 5, 0, 5);
|
||||
BorderSizePixel = 0;
|
||||
BackgroundColor3 = Color3.new(1,1,1);
|
||||
BackgroundTransparency = 1.0;
|
||||
Parent = this.HealthContainer;
|
||||
};
|
||||
|
||||
local healthFill = Util.Create'Frame'{
|
||||
Name = "HealthFill";
|
||||
Size = UDim2.new(1, 0, 1, 0);
|
||||
Position = UDim2.new(0, 0, 0, 0);
|
||||
BorderSizePixel = 0;
|
||||
BackgroundTransparency = 0.0;
|
||||
BackgroundColor3 = HEALTH_GREEN_COLOR;
|
||||
Parent = healthFillHolder;
|
||||
};
|
||||
|
||||
local healthText = Util.Create'TextLabel'{
|
||||
Name = "HealthText";
|
||||
Size = UDim2.new(0, 98, 0, 50);
|
||||
Position = UDim2.new(0, -100, 0, 0);
|
||||
BackgroundTransparency = 0.5;
|
||||
BackgroundColor3 = Color3.new(0,0,0);
|
||||
Font = Enum.Font.SourceSans;
|
||||
FontSize = Enum.FontSize.Size36;
|
||||
Text = "Health";
|
||||
TextColor3 = Color3.new(1,1,1);
|
||||
BorderSizePixel = 0;
|
||||
Parent = this.HealthContainer;
|
||||
};
|
||||
|
||||
local username = Util.Create'TextLabel'{
|
||||
Visible = false
|
||||
}
|
||||
|
||||
addToDisplayStack(this.HealthContainer)
|
||||
createContainer()
|
||||
|
||||
return this.Container, username, this.HealthContainer, healthFill
|
||||
end
|
||||
|
||||
function this:SetupTopStat()
|
||||
local topStatEnabled = true
|
||||
local displayedStat = nil
|
||||
local displayedStatChangedCon = nil
|
||||
local displayedStatParentedCon = nil
|
||||
local leaderstatsChildAddedCon = nil
|
||||
local tenFootInterfaceStat = nil
|
||||
|
||||
local function makeTenFootInterfaceStat()
|
||||
if tenFootInterfaceStat then return end
|
||||
|
||||
tenFootInterfaceStat = Util.Create'Frame'{
|
||||
Name = "OneStatFrame";
|
||||
Size = UDim2.new(1, 0, 0, 36);
|
||||
Position = UDim2.new(0, 0, 0, 0);
|
||||
BorderSizePixel = 0;
|
||||
BackgroundTransparency = 1;
|
||||
};
|
||||
local statName = Util.Create'TextLabel'{
|
||||
Name = "StatName";
|
||||
Size = UDim2.new(0.5,0,0,36);
|
||||
BackgroundTransparency = 1;
|
||||
Font = Enum.Font.SourceSans;
|
||||
FontSize = Enum.FontSize.Size36;
|
||||
TextStrokeColor3 = Color3.new(104/255, 104/255, 104/255);
|
||||
TextStrokeTransparency = 0;
|
||||
Text = " StatName:";
|
||||
TextColor3 = Color3.new(1,1,1);
|
||||
TextXAlignment = Enum.TextXAlignment.Left;
|
||||
BorderSizePixel = 0;
|
||||
ClipsDescendants = true;
|
||||
Parent = tenFootInterfaceStat;
|
||||
};
|
||||
local statValue = statName:clone()
|
||||
statValue.Position = UDim2.new(0.5,0,0,0)
|
||||
statValue.Name = "StatValue"
|
||||
statValue.Text = "123,643,231"
|
||||
statValue.TextXAlignment = Enum.TextXAlignment.Right
|
||||
statValue.Parent = tenFootInterfaceStat
|
||||
|
||||
addToDisplayStack(tenFootInterfaceStat)
|
||||
end
|
||||
|
||||
local function setDisplayedStat(newStat)
|
||||
if displayedStatChangedCon then displayedStatChangedCon:disconnect() displayedStatChangedCon = nil end
|
||||
if displayedStatParentedCon then displayedStatParentedCon:disconnect() displayedStatParentedCon = nil end
|
||||
|
||||
displayedStat = newStat
|
||||
|
||||
if displayedStat then
|
||||
makeTenFootInterfaceStat()
|
||||
updateTenFootStat(displayedStat)
|
||||
displayedStatParentedCon = displayedStat.AncestryChanged:connect(function() updateTenFootStat(displayedStat, "Parent") end)
|
||||
displayedStatChangedCon = displayedStat.Changed:connect(function(prop) updateTenFootStat(displayedStat, prop) end)
|
||||
end
|
||||
end
|
||||
|
||||
function updateTenFootStat(statObj, property)
|
||||
if property and property == "Parent" then
|
||||
tenFootInterfaceStat.StatName.Text = ""
|
||||
tenFootInterfaceStat.StatValue.Text = ""
|
||||
setDisplayedStat(nil)
|
||||
|
||||
tenFootInterfaceChanged()
|
||||
else
|
||||
if topStatEnabled then
|
||||
tenFootInterfaceStat.StatName.Text = " " .. tostring(statObj.Name) .. ":"
|
||||
tenFootInterfaceStat.StatValue.Text = tostring(statObj.Value)
|
||||
else
|
||||
tenFootInterfaceStat.StatName.Text = ""
|
||||
tenFootInterfaceStat.StatValue.Text = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function isValidStat(obj)
|
||||
return obj:IsA('StringValue') or obj:IsA('IntValue') or obj:IsA('BoolValue') or obj:IsA('NumberValue') or
|
||||
obj:IsA('DoubleConstrainedValue') or obj:IsA('IntConstrainedValue')
|
||||
end
|
||||
|
||||
local function tenFootInterfaceNewStat( newStat )
|
||||
if not displayedStat and isValidStat(newStat) then
|
||||
setDisplayedStat(newStat)
|
||||
end
|
||||
end
|
||||
|
||||
function tenFootInterfaceChanged()
|
||||
game:WaitForChild("Players")
|
||||
while not game.Players.LocalPlayer do
|
||||
wait()
|
||||
end
|
||||
|
||||
local leaderstats = game.Players.LocalPlayer:FindFirstChild('leaderstats')
|
||||
if leaderstats then
|
||||
local statChildren = leaderstats:GetChildren()
|
||||
for i = 1, #statChildren do
|
||||
tenFootInterfaceNewStat(statChildren[i])
|
||||
end
|
||||
if leaderstatsChildAddedCon then leaderstatsChildAddedCon:disconnect() end
|
||||
leaderstatsChildAddedCon = leaderstats.ChildAdded:connect(function(newStat)
|
||||
tenFootInterfaceNewStat(newStat)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
game:WaitForChild("Players")
|
||||
while not game.Players.LocalPlayer do
|
||||
wait()
|
||||
end
|
||||
|
||||
local leaderstats = game.Players.LocalPlayer:FindFirstChild('leaderstats')
|
||||
if leaderstats then
|
||||
tenFootInterfaceChanged()
|
||||
else
|
||||
game.Players.LocalPlayer.ChildAdded:connect(tenFootInterfaceChanged)
|
||||
end
|
||||
|
||||
--Top Stat Public API
|
||||
|
||||
local topStatApiTable = {}
|
||||
|
||||
function topStatApiTable:SetTopStatEnabled(value)
|
||||
topStatEnabled = value
|
||||
if displayedStat then
|
||||
updateTenFootStat(displayedStat, "")
|
||||
end
|
||||
end
|
||||
|
||||
return topStatApiTable
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
|
||||
-- Public API
|
||||
|
||||
local moduleApiTable = {}
|
||||
|
||||
local TenFootInterfaceModule = CreateModule()
|
||||
|
||||
function moduleApiTable:IsEnabled()
|
||||
return tenFootInterfaceEnabled
|
||||
end
|
||||
|
||||
function moduleApiTable:CreateHealthBar()
|
||||
return TenFootInterfaceModule:CreateHealthBar()
|
||||
end
|
||||
|
||||
function moduleApiTable:SetupTopStat()
|
||||
return TenFootInterfaceModule:SetupTopStat()
|
||||
end
|
||||
|
||||
return moduleApiTable
|
||||
@@ -0,0 +1,808 @@
|
||||
--Panel3D: 3D GUI panels for VR
|
||||
--written by 0xBAADF00D
|
||||
--revised/refactored 5/11/16
|
||||
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local RunService = game:GetService("RunService")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
local PlayersService = game.Players
|
||||
|
||||
|
||||
--Util
|
||||
local Util = {}
|
||||
do
|
||||
function Util.Create(instanceType)
|
||||
return function(data)
|
||||
local obj = Instance.new(instanceType)
|
||||
for k, v in pairs(data) do
|
||||
if type(k) == 'number' then
|
||||
v.Parent = obj
|
||||
else
|
||||
obj[k] = v
|
||||
end
|
||||
end
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
-- RayPlaneIntersection (shortened)
|
||||
-- http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
|
||||
function Util.RayPlaneIntersection(ray, planeNormal, pointOnPlane)
|
||||
planeNormal = planeNormal.unit
|
||||
ray = ray.Unit
|
||||
|
||||
local Vd = planeNormal:Dot(ray.Direction)
|
||||
if Vd == 0 then -- parallel, no intersection
|
||||
return nil
|
||||
end
|
||||
|
||||
local V0 = planeNormal:Dot(pointOnPlane - ray.Origin)
|
||||
local t = V0 / Vd
|
||||
if t < 0 then --plane is behind ray origin, and thus there is no intersection
|
||||
return nil
|
||||
end
|
||||
|
||||
return ray.Origin + ray.Direction * t
|
||||
end
|
||||
end
|
||||
--End of Util
|
||||
|
||||
|
||||
--Panel3D State variables
|
||||
local renderStepName = "Panel3DRenderStep-" .. game:GetService("HttpService"):GenerateGUID()
|
||||
local defaultPixelsPerStud = 64
|
||||
local pointUpCF = CFrame.Angles(math.rad(-90), math.rad(180), 0)
|
||||
local zeroVector = Vector3.new(0, 0, 0)
|
||||
local zeroVector2 = Vector2.new(0, 0)
|
||||
local turnAroundCF = CFrame.Angles(0, math.rad(180), 0)
|
||||
local fullyOpaqueAtPixelsFromEdge = 10
|
||||
local fullyTransparentAtPixelsFromEdge = 80
|
||||
local partThickness = 0.2
|
||||
|
||||
local cursorHidden = false
|
||||
local cursorHideTime = 2.5
|
||||
|
||||
local currentModal = nil
|
||||
local lastModal = nil
|
||||
local currentMaxDist = math.huge
|
||||
local currentClosest = nil
|
||||
local lastClosest = nil
|
||||
local currentHeadScale = 1
|
||||
local panels = {}
|
||||
local floorRotation = CFrame.new()
|
||||
local cursor = Util.Create "ImageLabel" {
|
||||
Image = "rbxasset://textures/Cursors/Gamepad/Pointer.png",
|
||||
Size = UDim2.new(0, 8, 0, 8),
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 10
|
||||
}
|
||||
--End of Panel3D State variables
|
||||
|
||||
|
||||
--Panel3D Declaration and enumerations
|
||||
local Panel3D = {}
|
||||
Panel3D.Type = {
|
||||
None = 0,
|
||||
Floor = 1,
|
||||
Fixed = 2,
|
||||
HorizontalFollow = 3,
|
||||
FixedToHead = 4
|
||||
}
|
||||
|
||||
Panel3D.OnPanelClosed = Util.Create 'BindableEvent' {
|
||||
Name = 'OnPanelClosed'
|
||||
}
|
||||
|
||||
function Panel3D.GetHeadLookXZ(withTranslation)
|
||||
local userHeadCF = UserInputService:GetUserCFrame(Enum.UserCFrame.Head)
|
||||
local headLook = userHeadCF.lookVector
|
||||
local headYaw = math.atan2(-headLook.Z, headLook.X) + math.rad(90)
|
||||
local cf = CFrame.Angles(0, headYaw, 0)
|
||||
|
||||
if withTranslation then
|
||||
cf = cf + userHeadCF.p
|
||||
end
|
||||
return cf
|
||||
end
|
||||
|
||||
function Panel3D.FindContainerOf(element)
|
||||
for i, v in pairs(panels) do
|
||||
if v.gui and v.gui:IsAncestorOf(element) then
|
||||
return v
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Panel3D.SetModalPanel(panel)
|
||||
if currentModal == panel then
|
||||
return
|
||||
end
|
||||
if currentModal then
|
||||
currentModal:OnModalChanged(false)
|
||||
end
|
||||
if panel then
|
||||
panel:OnModalChanged(true)
|
||||
end
|
||||
lastModal = currentModal
|
||||
currentModal = panel
|
||||
end
|
||||
--End of Panel3D Declaration and enumerations
|
||||
|
||||
|
||||
--Cursor autohiding methods
|
||||
local cursorHidden = false
|
||||
local hasTool = false
|
||||
local lastMouseActivity = tick()
|
||||
local lastMouseBehavior = Enum.MouseBehavior.Default
|
||||
|
||||
local function OnCharacterAdded(character)
|
||||
hasTool = false
|
||||
for i, v in ipairs(character:GetChildren()) do
|
||||
if v:IsA("Tool") then
|
||||
hasTool = true
|
||||
end
|
||||
end
|
||||
character.ChildAdded:connect(function(child)
|
||||
if child:IsA("Tool") then
|
||||
hasTool = true
|
||||
lastMouseActivity = tick() --kick the mouse when a tool is equipped
|
||||
end
|
||||
end)
|
||||
character.ChildRemoved:connect(function(child)
|
||||
if child:IsA("Tool") then
|
||||
hasTool = false
|
||||
end
|
||||
end)
|
||||
end
|
||||
spawn(function()
|
||||
while not PlayersService.LocalPlayer do wait() end
|
||||
PlayersService.LocalPlayer.CharacterAdded:connect(OnCharacterAdded)
|
||||
if PlayersService.LocalPlayer.Character then OnCharacterAdded(PlayersService.LocalPlayer.Character) end
|
||||
end)
|
||||
local function autoHideCursor(hide)
|
||||
if not UserInputService.VREnabled then
|
||||
cursorHidden = false
|
||||
return
|
||||
end
|
||||
if hide then
|
||||
--don't hide if there's a tool in the character
|
||||
local character = PlayersService.LocalPlayer.Character
|
||||
if character and hasTool then
|
||||
return
|
||||
end
|
||||
cursorHidden = true
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
|
||||
else
|
||||
cursorHidden = false
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None
|
||||
end
|
||||
end
|
||||
|
||||
local function isCursorVisible()
|
||||
--if ForceShow, the cursor is definitely visible at all times
|
||||
if UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.ForceShow then
|
||||
return true
|
||||
end
|
||||
--if ForceHide, the cursor is definitely NOT visible
|
||||
if UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.ForceHide then
|
||||
return false
|
||||
end
|
||||
--Otherwise, we need to check if the developer set MouseIconEnabled=false
|
||||
if UserInputService.MouseIconEnabled and UserInputService.OverrideMouseIconBehavior == Enum.OverrideMouseIconBehavior.None then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--End of cursor autohiding methods
|
||||
|
||||
|
||||
--Panel class implementation
|
||||
local Panel = {}
|
||||
Panel.mt = { __index = Panel }
|
||||
function Panel.new(name)
|
||||
local instance = {
|
||||
name = name,
|
||||
|
||||
part = nil,
|
||||
gui = nil,
|
||||
|
||||
width = 1,
|
||||
height = 1,
|
||||
|
||||
isVisible = false,
|
||||
isEnabled = false,
|
||||
panelType = Panel3D.Type.None,
|
||||
pixelScale = 1,
|
||||
showCursor = true,
|
||||
canFade = true,
|
||||
shouldFindLookAtGuiElement = false,
|
||||
ignoreModal = false,
|
||||
|
||||
linkedTo = nil,
|
||||
subpanels = {},
|
||||
|
||||
transparency = 1,
|
||||
forceShowUntilLookedAt = false,
|
||||
isLookedAt = false,
|
||||
isOffscreen = true,
|
||||
lookAtPixel = Vector2.new(-1, -1),
|
||||
lookAtDistance = math.huge,
|
||||
lookAtGuiElement = nil,
|
||||
isClosest = true
|
||||
}
|
||||
|
||||
if panels[name] then
|
||||
error("A panel by the name of " .. name .. " already exists.")
|
||||
end
|
||||
panels[name] = instance
|
||||
|
||||
return setmetatable(instance, Panel.mt)
|
||||
end
|
||||
|
||||
--Panel accessor methods
|
||||
function Panel:GetPart()
|
||||
if not self.part then
|
||||
self.part = Util.Create "Part" {
|
||||
Name = self.name,
|
||||
Parent = nil,
|
||||
|
||||
Transparency = 1,
|
||||
|
||||
CanCollide = false,
|
||||
Anchored = true,
|
||||
Archivable = false,
|
||||
|
||||
Size = Vector3.new(1, 1, partThickness)
|
||||
}
|
||||
end
|
||||
return self.part
|
||||
end
|
||||
|
||||
function Panel:GetGUI()
|
||||
if not self.gui then
|
||||
local part = self:GetPart()
|
||||
self.gui = Util.Create "SurfaceGui" {
|
||||
Parent = CoreGui,
|
||||
Adornee = part,
|
||||
Active = true,
|
||||
ToolPunchThroughDistance = 1000,
|
||||
CanvasSize = Vector2.new(0, 0),
|
||||
Enabled = self.isEnabled,
|
||||
AlwaysOnTop = true
|
||||
}
|
||||
end
|
||||
return self.gui
|
||||
end
|
||||
|
||||
function Panel:FindHoveredGuiElement(elements)
|
||||
local x, y = self.lookAtPixel.X, self.lookAtPixel.Y
|
||||
for i, v in pairs(elements) do
|
||||
local minPt = v.AbsolutePosition
|
||||
local maxPt = v.AbsolutePosition + v.AbsoluteSize
|
||||
if minPt.X <= x and maxPt.X >= x and
|
||||
minPt.Y <= y and maxPt.Y >= y then
|
||||
return v, i
|
||||
end
|
||||
end
|
||||
end
|
||||
--End of panel accessor methods
|
||||
|
||||
|
||||
--Panel update methods
|
||||
function Panel:SetPartCFrame(cframe)
|
||||
self:GetPart().CFrame = cframe * CFrame.new(0, 0, -0.5 * partThickness)
|
||||
end
|
||||
|
||||
function Panel:SetEnabled(enabled)
|
||||
if self.isEnabled == enabled then
|
||||
return
|
||||
end
|
||||
|
||||
self.isEnabled = enabled
|
||||
if enabled then
|
||||
self:GetPart().Parent = workspace.CurrentCamera --todo: Perhaps this can change soon.
|
||||
self:GetGUI().Enabled = true
|
||||
for i, v in pairs(self.subpanels) do
|
||||
v:GetPart().Parent = workspace.CurrentCamera
|
||||
v:GetGUI().Enabled = true
|
||||
end
|
||||
else
|
||||
self:GetPart().Parent = nil
|
||||
self:GetGUI().Enabled = false
|
||||
for i, v in pairs(self.subpanels) do
|
||||
v:GetPart().Parent = nil
|
||||
v:GetGUI().Enabled = false
|
||||
end
|
||||
end
|
||||
|
||||
self:OnEnabled(enabled)
|
||||
end
|
||||
|
||||
function Panel:EvaluatePositioning(cameraCF, cameraRenderCF, userHeadCF)
|
||||
if self.panelType == Panel3D.Type.Floor then
|
||||
--Floor panels simply... go on the floor.
|
||||
--Panel will be in camera's local space (which is assumed to be horizontal),
|
||||
--and floorRotation is derived from the user's head rotation (yaw only)
|
||||
local floorCF = cameraCF * CFrame.new(0, -headHeightFromFloor * currentHeadScale, 0) * floorRotation
|
||||
self:SetPartCFrame(floorCF * pointUpCF)
|
||||
elseif self.panelType == Panel3D.Type.Fixed then
|
||||
--Places the panel in the camera's local space, but doesn't follow the user's head.
|
||||
--Useful if you know what you're doing. localCF can be updated in PreUpdate for animation.
|
||||
local cf = self.localCF - self.localCF.p
|
||||
cf = cf + (self.localCF.p * currentHeadScale)
|
||||
self:SetPartCFrame(cameraCF * cf)
|
||||
elseif self.panelType == Panel3D.Type.HorizontalFollow then
|
||||
local headLook = userHeadCF.lookVector
|
||||
local headYaw = math.atan2(-headLook.Z, headLook.X) + math.rad(90)
|
||||
local headForwardCF = CFrame.Angles(0, headYaw, 0) + userHeadCF.p
|
||||
local localCF = (headForwardCF * self.angleFromForward) * --Rotate about Y (left-right)
|
||||
self.angleFromHorizon * --Rotate about X (up-down)
|
||||
(currentHeadScale * self.distance) * --Move into scene
|
||||
turnAroundCF --Turn around to face character
|
||||
self:SetPartCFrame(cameraCF * localCF)
|
||||
elseif self.panelType == Panel3D.Type.FixedToHead then
|
||||
--Places the panel in the user's head local space. localCF can be updated in PreUpdate for animation.
|
||||
local cf = self.localCF - self.localCF.p
|
||||
cf = cf + (self.localCF.p * currentHeadScale)
|
||||
self:SetPartCFrame(cameraRenderCF * cf)
|
||||
end
|
||||
end
|
||||
|
||||
function Panel:EvaluateGaze(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
||||
--reset distance data
|
||||
self.isClosest = false
|
||||
self.lookAtPixel = zeroVector2
|
||||
self.lookAtDistance = math.huge
|
||||
|
||||
--Evaluate the lookRay versus this panel
|
||||
local planeCF = self:GetPart().CFrame
|
||||
local planeNormal = planeCF.lookVector
|
||||
local pointOnPlane = planeCF.p + (planeNormal * partThickness * 0.5) --Move the point out by half the thickness of the part
|
||||
|
||||
local worldIntersectPoint = Util.RayPlaneIntersection(lookRay, planeNormal, pointOnPlane)
|
||||
if worldIntersectPoint then
|
||||
self.isOffscreen = false
|
||||
|
||||
--transform worldIntersectPoint to gui space
|
||||
local gui = self:GetGUI()
|
||||
local guiWidth, guiHeight = gui.AbsoluteSize.X, gui.AbsoluteSize.Y
|
||||
local localIntersectPoint = (planeCF:pointToObjectSpace(worldIntersectPoint) / currentHeadScale) * Vector3.new(-1, 1, 1) + Vector3.new(self.width / 2, -self.height / 2, 0)
|
||||
self.lookAtPixel = Vector2.new((localIntersectPoint.X / self.width) * gui.AbsoluteSize.X, (localIntersectPoint.Y / self.height) * -gui.AbsoluteSize.Y)
|
||||
|
||||
--fire mouse enter/leave events if necessary
|
||||
local guiX, guiY = self.lookAtPixel.X, self.lookAtPixel.Y
|
||||
if guiX >= 0 and guiX <= guiWidth and
|
||||
guiY >= 0 and guiY <= guiHeight then
|
||||
if not self.isLookedAt then
|
||||
self.isLookedAt = true
|
||||
self:OnMouseEnter(guiX, guiY)
|
||||
if self.forceShowUntilLookedAt then
|
||||
self.forceShowUntilLookedAt = false
|
||||
end
|
||||
end
|
||||
else
|
||||
if self.isLookedAt then
|
||||
self.isLookedAt = false
|
||||
self:OnMouseLeave(guiX, guiY)
|
||||
end
|
||||
end
|
||||
|
||||
--evaluate distance
|
||||
self.lookAtDistance = (worldIntersectPoint - cameraRenderCF.p).magnitude
|
||||
if self.isLookedAt and self.lookAtDistance < currentMaxDist and self.showCursor then
|
||||
currentMaxDist = self.lookAtDistance
|
||||
currentClosest = self
|
||||
end
|
||||
else
|
||||
self.isOffscreen = true
|
||||
|
||||
--Not looking at the plane at all, so fire off mouseleave if necessary.
|
||||
if self.isLookedAt then
|
||||
self.isLookedAt = false
|
||||
self:OnMouseLeave(self.lookAtPixel.X, self.lookAtPixel.Y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Panel:EvaluateTransparency()
|
||||
--Early exit if force shown
|
||||
if self.forceShowUntilLookedAt then
|
||||
self.transparency = 0
|
||||
return
|
||||
end
|
||||
--Early exit if we're looking at the panel (no transparency!)
|
||||
if self.isLookedAt then
|
||||
self.transparency = 0
|
||||
return
|
||||
end
|
||||
--Similarly, exit if we can't possibly see the panel.
|
||||
if self.isOffscreen then
|
||||
self.transparency = 1
|
||||
return
|
||||
end
|
||||
--Otherwise, we'll want to calculate the transparency.
|
||||
self.transparency = self:CalculateTransparency()
|
||||
end
|
||||
|
||||
function Panel:Update(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
||||
if self.forceShowUntilLookedAt and not self.part then
|
||||
self:GetPart()
|
||||
self:GetGUI()
|
||||
end
|
||||
if not self.part then
|
||||
return
|
||||
end
|
||||
|
||||
local isModal = (currentModal == self)
|
||||
if not isModal and self.linkedTo and self.linkedTo == currentModal then
|
||||
isModal = true
|
||||
end
|
||||
if currentModal and not isModal then
|
||||
self:SetEnabled(false)
|
||||
return
|
||||
end
|
||||
|
||||
self:PreUpdate(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
||||
if self.isVisible then
|
||||
self:EvaluatePositioning(cameraCF, cameraRenderCF, userHeadCF)
|
||||
self:EvaluateGaze(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
||||
|
||||
self:EvaluateTransparency(cameraCF, cameraRenderCF)
|
||||
end
|
||||
end
|
||||
--End of Panel update methods
|
||||
|
||||
--Panel virtual methods
|
||||
function Panel:PreUpdate() --virtual: handle positioning here
|
||||
end
|
||||
|
||||
function Panel:OnUpdate() --virtual: handle transparency here
|
||||
end
|
||||
|
||||
function Panel:OnMouseEnter(x, y) --virtual
|
||||
end
|
||||
|
||||
function Panel:OnMouseLeave(x, y) --virtual
|
||||
end
|
||||
|
||||
function Panel:OnEnabled(enabled) --virtual
|
||||
end
|
||||
|
||||
function Panel:OnModalChanged(isModal) --virtual
|
||||
end
|
||||
|
||||
function Panel:OnVisibilityChanged(visible) --virtual
|
||||
end
|
||||
|
||||
function Panel:CalculateTransparency() --virtual
|
||||
if not self.canFade then
|
||||
return 0
|
||||
end
|
||||
|
||||
local guiWidth, guiHeight = self.gui.AbsoluteSize.X, self.gui.AbsoluteSize.Y
|
||||
local lookX, lookY = self.lookAtPixel.X, self.lookAtPixel.Y
|
||||
|
||||
--Determine the distance from the edge;
|
||||
--if x is negative it's on the left side, meaning the distance is just absolute value
|
||||
--if x is positive it's on the right side, meaning the distance is x minus the width
|
||||
local xEdgeDist = lookX < 0 and -lookX or (lookX - guiWidth)
|
||||
local yEdgeDist = lookY < 0 and -lookY or (lookY - guiHeight)
|
||||
if lookX > 0 and lookX < guiWidth then
|
||||
xEdgeDist = 0
|
||||
end
|
||||
if lookY > 0 and lookY < guiHeight then
|
||||
yEdgeDist = 0
|
||||
end
|
||||
local edgeDist = math.sqrt(xEdgeDist ^ 2 + yEdgeDist ^ 2)
|
||||
|
||||
--since transparency is 0-1, we know how many pixels will give us 0 and how many will give us 1.
|
||||
local offset = fullyOpaqueAtPixelsFromEdge
|
||||
local interval = fullyTransparentAtPixelsFromEdge
|
||||
--then we just clamp between 0 and 1.
|
||||
return math.max(0, math.min(1, (edgeDist - offset) / interval))
|
||||
end
|
||||
--End of Panel virtual methods
|
||||
|
||||
|
||||
--Panel configuration methods
|
||||
function Panel:ResizeStuds(width, height, pixelsPerStud)
|
||||
pixelsPerStud = pixelsPerStud or defaultPixelsPerStud
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.pixelScale = pixelsPerStud / defaultPixelsPerStud
|
||||
|
||||
local part = self:GetPart()
|
||||
part.Size = Vector3.new(self.width * currentHeadScale, self.height * currentHeadScale, partThickness)
|
||||
|
||||
local gui = self:GetGUI()
|
||||
gui.CanvasSize = Vector2.new(pixelsPerStud * self.width, pixelsPerStud * self.height)
|
||||
end
|
||||
|
||||
function Panel:ResizePixels(width, height, pixelsPerStud)
|
||||
pixelsPerStud = pixelsPerStud or defaultPixelsPerStud
|
||||
|
||||
local widthInStuds = width / pixelsPerStud
|
||||
local heightInStuds = height / pixelsPerStud
|
||||
self:ResizeStuds(widthInStuds, heightInStuds, pixelsPerStud)
|
||||
end
|
||||
|
||||
function Panel:OnHeadScaleChanged(newHeadScale)
|
||||
local pixelsPerStud = self.pixelScale * defaultPixelsPerStud
|
||||
self:ResizeStuds(self.width, self.height, pixelsPerStud)
|
||||
end
|
||||
|
||||
function Panel:SetType(panelType, config)
|
||||
self.panelType = panelType
|
||||
|
||||
--clear out old type-specific members
|
||||
self.floorPos = nil
|
||||
|
||||
self.localCF = nil
|
||||
|
||||
self.angleFromHorizon = nil
|
||||
self.angleFromForward = nil
|
||||
self.distance = nil
|
||||
|
||||
if not config then
|
||||
config = {}
|
||||
end
|
||||
|
||||
if panelType == Panel3D.Type.None then
|
||||
--nothing to do
|
||||
return
|
||||
elseif panelType == Panel3D.Type.Floor then
|
||||
self.floorPos = config.FloorPosition or Vector3.new(0, 0, 0)
|
||||
elseif panelType == Panel3D.Type.Fixed then
|
||||
self.localCF = config.CFrame or CFrame.new()
|
||||
elseif panelType == Panel3D.Type.HorizontalFollow then
|
||||
self.angleFromHorizon = CFrame.Angles(config.angleFromHorizon or 0, 0, 0)
|
||||
self.angleFromForward = CFrame.Angles(0, config.angleFromForward or 0, 0)
|
||||
self.distance = CFrame.new(0, 0, config.distance or 5)
|
||||
elseif panelType == Panel3D.Type.FixedToHead then
|
||||
self.localCF = config.CFrame or CFrame.new()
|
||||
else
|
||||
error("Invalid Panel type")
|
||||
end
|
||||
end
|
||||
|
||||
function Panel:SetVisible(visible, modal)
|
||||
if visible ~= self.isVisible then
|
||||
self:OnVisibilityChanged(visible)
|
||||
if not visible then
|
||||
Panel3D.OnPanelClosed:Fire(self.name)
|
||||
end
|
||||
end
|
||||
|
||||
self.isVisible = visible
|
||||
self:SetEnabled(visible)
|
||||
if visible and modal then
|
||||
Panel3D.SetModalPanel(self)
|
||||
end
|
||||
if not visible and currentModal == self then
|
||||
if modal and not doNotRestore then
|
||||
--restore last modal panel
|
||||
Panel3D.SetModalPanel(lastModal)
|
||||
else
|
||||
Panel3D.SetModalPanel(nil)
|
||||
|
||||
--if the coder explicitly wanted to hide this modal panel,
|
||||
--it follows that they don't want it to be restored when the next
|
||||
--modal panel is hidden.
|
||||
if lastModal == self then
|
||||
lastModal = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not visible and self.forceShowUntilLookedAt then
|
||||
self.forceShowUntilLookedAt = false
|
||||
end
|
||||
end
|
||||
|
||||
function Panel:LinkTo(panelName)
|
||||
if type(panelName) == "string" then
|
||||
self.linkedTo = Panel3D.Get(panelName)
|
||||
else
|
||||
self.linkedTo = panelName
|
||||
end
|
||||
end
|
||||
|
||||
function Panel:ForceShowUntilLookedAt(makeModal)
|
||||
--ensure the part exists
|
||||
self:GetPart()
|
||||
self:GetGUI()
|
||||
|
||||
self:SetVisible(true, makeModal)
|
||||
self.forceShowUntilLookedAt = true
|
||||
end
|
||||
|
||||
function Panel:SetCanFade(canFade)
|
||||
self.canFade = canFade
|
||||
end
|
||||
|
||||
--Child class, Subpanel
|
||||
local Subpanel = {}
|
||||
local Subpanel_mt = {}
|
||||
function Subpanel.new(guiElement)
|
||||
local instance = {
|
||||
guiElement = guiElement
|
||||
}
|
||||
return setmetatable(instance, Subpanel_mt)
|
||||
end
|
||||
|
||||
function Subpanel:GetPart()
|
||||
warn("Subpanel:GetPart() not yet implemented")
|
||||
return nil
|
||||
end
|
||||
|
||||
function Subpanel:GetGUI()
|
||||
warn("Subpanel:GetGUI() not yet implemented")
|
||||
return nil
|
||||
end
|
||||
|
||||
function Panel:AddSubpanel(guiElement)
|
||||
local subpanel = Subpanel.new(guiElement)
|
||||
self.subpanels[guiElement] = subpanel
|
||||
end
|
||||
|
||||
function Panel:RemoveSubpanel(guiElement)
|
||||
self.subpanels[guiElement] = nil
|
||||
end
|
||||
--End of Panel configuration methods
|
||||
--End of Panel class implementation
|
||||
|
||||
|
||||
--Panel3D API
|
||||
function Panel3D.Get(name)
|
||||
local panel = panels[name]
|
||||
if not panels[name] then
|
||||
panels[name] = Panel.new(name)
|
||||
panel = panels[name]
|
||||
end
|
||||
return panel
|
||||
end
|
||||
--End of Panel3D API
|
||||
|
||||
|
||||
--Panel3D Setup
|
||||
local function onRenderStep()
|
||||
if not UserInputService.VREnabled then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
--reset distance info
|
||||
currentClosest = nil
|
||||
currentMaxDist = math.huge
|
||||
|
||||
--figure out some useful stuff
|
||||
local camera = workspace.CurrentCamera
|
||||
local cameraCF = camera.CFrame
|
||||
local cameraRenderCF = camera:GetRenderCFrame()
|
||||
local userHeadCF = UserInputService:GetUserCFrame(Enum.UserCFrame.Head)
|
||||
local lookRay = Ray.new(cameraRenderCF.p, cameraRenderCF.lookVector)
|
||||
|
||||
--allow all panels to run their own update code
|
||||
for i, v in pairs(panels) do
|
||||
v:Update(cameraCF, cameraRenderCF, userHeadCF, lookRay)
|
||||
end
|
||||
|
||||
--evaluate linked panels
|
||||
local processed = {}
|
||||
for i, v in pairs(panels) do
|
||||
if not processed[v] and v.linkedTo and v.isVisible and v.linkedTo.isVisible then
|
||||
processed[v] = true
|
||||
processed[v.linkedTo] = true
|
||||
|
||||
local minTransparency = math.min(v.transparency, v.linkedTo.transparency)
|
||||
v.transparency = minTransparency
|
||||
v.linkedTo.transparency = minTransparency
|
||||
end
|
||||
end
|
||||
|
||||
--run post update because the distance information hasn't been
|
||||
--finalized until now.
|
||||
for i, v in pairs(panels) do
|
||||
--If the part is fully transparent, we don't want to keep it around in the workspace.
|
||||
if v.part and v.gui then
|
||||
--check if this panel is the current modal panel
|
||||
local isModal = (currentModal == v)
|
||||
--but also check if this panel is linked to the current modal panel
|
||||
if not isModal and v.linkedTo and v.linkedTo == currentModal then
|
||||
isModal = true
|
||||
end
|
||||
|
||||
local show = v.isVisible
|
||||
if not isModal and currentModal then
|
||||
show = false
|
||||
end
|
||||
if v.transparency >= 1 then
|
||||
show = false
|
||||
end
|
||||
|
||||
if v.forceShowUntilLookedAt then
|
||||
show = true
|
||||
end
|
||||
if not v.canFade and v.isVisible then
|
||||
show = true
|
||||
end
|
||||
|
||||
v:SetEnabled(show)
|
||||
end
|
||||
|
||||
v:OnUpdate()
|
||||
end
|
||||
|
||||
--place the cursor on the closest panel (for now)
|
||||
if not currentClosest and lastClosest then
|
||||
UserInputService.MouseBehavior = lastMouseBehavior
|
||||
elseif currentClosest and not lastClosest then
|
||||
lastMouseBehavior = UserInputService.MouseBehavior
|
||||
end
|
||||
|
||||
if currentClosest then
|
||||
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
|
||||
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
|
||||
cursor.Parent = currentClosest:GetGUI()
|
||||
|
||||
local x, y = currentClosest.lookAtPixel.X, currentClosest.lookAtPixel.Y
|
||||
cursor.Size = UDim2.new(0, 8 * currentClosest.pixelScale, 0, 8 * currentClosest.pixelScale)
|
||||
cursor.Position = UDim2.new(0, x - cursor.AbsoluteSize.x * 0.5, 0, y - cursor.AbsoluteSize.y * 0.5)
|
||||
else
|
||||
cursor.Parent = nil
|
||||
end
|
||||
lastClosest = currentClosest
|
||||
end
|
||||
RunService:BindToRenderStep(renderStepName, Enum.RenderPriority.Last.Value, onRenderStep)
|
||||
|
||||
--Implement cursor autohide functionality
|
||||
UserInputService.InputChanged:connect(function(inputObj, processed)
|
||||
if inputObj.UserInputType == Enum.UserInputType.MouseMovement then
|
||||
lastMouseActivity = tick()
|
||||
autoHideCursor(false)
|
||||
end
|
||||
end)
|
||||
local function onHeartbeat()
|
||||
if isCursorVisible() then
|
||||
cursorHidden = false
|
||||
end
|
||||
if lastMouseActivity + cursorHideTime < tick() and not GuiService.MenuIsOpen and not cursorHidden then
|
||||
autoHideCursor(true)
|
||||
end
|
||||
end
|
||||
RunService.Heartbeat:connect(onHeartbeat)
|
||||
|
||||
--Handle camera changes
|
||||
local cameraChangedConnection = nil
|
||||
local function onCameraChanged(prop)
|
||||
if prop == "HeadScale" then
|
||||
pcall(function()
|
||||
currentHeadScale = workspace.CurrentCamera.HeadScale
|
||||
end)
|
||||
for i, v in pairs(panels) do
|
||||
v:OnHeadScaleChanged(currentHeadScale)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onWorkspaceChanged(prop)
|
||||
if prop == "CurrentCamera" then
|
||||
onCameraChanged("HeadScale")
|
||||
if cameraChangedConnection then
|
||||
cameraChangedConnection:disconnect()
|
||||
end
|
||||
cameraChangedConnection = workspace.CurrentCamera.Changed:connect(onCameraChanged)
|
||||
end
|
||||
end
|
||||
if workspace.CurrentCamera then
|
||||
onWorkspaceChanged("CurrentCamera")
|
||||
end
|
||||
workspace.Changed:connect(onWorkspaceChanged)
|
||||
|
||||
return Panel3D
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user