This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,5 @@
{
"lint": {
"ImportUnused": "fatal"
}
}
@@ -0,0 +1,8 @@
{
"lint": {
"SameLineStatement": "fatal",
"LocalShadow": "fatal",
"FunctionUnused": "fatal",
"ImplicitReturn": "fatal"
}
}
@@ -0,0 +1,346 @@
local VRService = game:GetService("VRService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui.RobloxGui
local CommonUtil = require(RobloxGui.Modules.Common.CommonUtil)
local PARTS_INFO = {
Body = {
size = Vector3.new(0.391, 0.277, 0.731),
meshId = "rbxassetid://433286977",
textureId = "rbxassetid://433287000",
offset = CFrame.new(0, -0.115, 0.213)
},
RightGrip = {
meshId = "rbxassetid://433290663",
textureId = "rbxassetid://433290688",
offset = CFrame.new(0.057, -0.047, 0.29),
moveOffset = CFrame.new(-0.005, 0, 0)
},
LeftGrip = {
meshId = "rbxassetid://433289821",
textureId = "rbxassetid://433289832",
offset = CFrame.new(-0.057, -0.047, 0.29),
moveOffset = CFrame.new(0.005, 0, 0)
},
SystemButton = {
meshId = "rbxassetid://433291265",
textureId = "rbxassetid://433291278",
offset = CFrame.new(0, 0.005, 0.294),
moveOffset = CFrame.new(Vector3.new(0, 0.997420907, 0.071774438) * -0.001)
},
MenuButton = {
meshId = "rbxassetid://433288124",
textureId = "rbxassetid://433288134",
offset = CFrame.new(0, 0.016, 0.068),
moveOffset = CFrame.new(Vector3.new(0, 0.999877751, -0.0156304892) * -0.001)
},
Trackpad = {
meshId = "rbxassetid://433288821",
textureId = "rbxassetid://433288836",
offset = CFrame.new(0, 0.000, 0.163),
moveOffset = CFrame.new(Vector3.new(0, 0.993506849, 0.113772281) * -0.001)
},
Trigger = {
meshId = "rbxassetid://433288767",
textureId = "rbxassetid://433288775",
offset = CFrame.new(0, -0.093, 0.163),
},
LED = {
meshId = "rbxassetid://433293218",
textureId = "rbxassetid://433293231",
offset = CFrame.new(0, 0.007, 0.32)
},
ButtonA = {
meshId = "rbxassetid://922444331",
textureId = "rbxasset://textures/ui/VR/ButtonA.png",
offset = CFrame.new(-0, 0.0115499999, 0.197307006) * CFrame.Angles(0, math.pi, 0),
moveOffset = CFrame.new(0, -0.005, 0)
},
ButtonB = {
meshId = "rbxassetid://922454202",
textureId = "rbxasset://textures/ui/VR/ButtonB.png",
offset = CFrame.new(0.0391042456, 0.0154935224, 0.161779419) * CFrame.Angles(0, math.pi, 0),
moveOffset = CFrame.new(0, -0.005, 0)
},
ButtonX = {
meshId = "rbxassetid://922453111",
textureId = "rbxasset://textures/ui/VR/ButtonX.png",
offset = CFrame.new(-0.0390719995, 0.0154770007, 0.161733001) * CFrame.Angles(0, math.pi, 0),
moveOffset = CFrame.new(0, -0.005, 0)
},
ButtonY = {
meshId = "rbxassetid://922455502",
textureId = "rbxasset://textures/ui/VR/ButtonY.png",
offset = CFrame.new(0, 0.0197229274, 0.126810834) * CFrame.Angles(0, math.pi, 0),
moveOffset = CFrame.new(0, -0.005, 0)
}
}
local MATERIAL = Enum.Material.Granite
local SCALE = Vector3.new(0.033, 0.033, 0.033)
local ViveController = {}
ViveController.__index = ViveController
local touchpadForUserCFrame = {
[Enum.UserCFrame.LeftHand] = Enum.VRTouchpad.Left,
[Enum.UserCFrame.RightHand] = Enum.VRTouchpad.Right
}
function ViveController.new(userCFrame)
local self = setmetatable({}, ViveController)
self.userCFrame = userCFrame
self.touchpad = touchpadForUserCFrame[self.userCFrame]
self.touchpadMode = VRService:GetTouchpadMode(self.touchpad)
self.onTouchpadModeChangedConn = VRService.TouchpadModeChanged:connect(function(...)
self:onTouchpadModeChanged(...)
end)
self.model = CommonUtil.Create("Model") {
Name = "ViveController",
Archivable = false
}
self.origin = CommonUtil.Create("Part") {
Parent = self.model,
Name = "Origin",
Anchored = false,
Transparency = 1,
Size = Vector3.new(0.05, 0.05, 0.05),
CanCollide = false
}
self.parts = {}
for partName, partInfo in pairs(PARTS_INFO) do
local partScale = SCALE
if partInfo.scale then
partScale = partScale * partInfo.scale
end
local part = CommonUtil.Create("Part") {
Parent = self.model,
Name = partName,
Anchored = false,
CanCollide = false,
Material = MATERIAL,
Size = partInfo.size or Vector3.new(0.05, 0.05, 0.05),
CFrame = self.origin.CFrame * partInfo.offset
}
local mesh = CommonUtil.Create("SpecialMesh") {
Parent = part,
Name = "Mesh",
MeshId = partInfo.meshId,
TextureId = partInfo.textureId,
Scale = partScale
}
local weld = CommonUtil.Create("Weld") {
Parent = part,
Name = "Weld",
Part0 = self.origin,
Part1 = part,
C0 = partInfo.offset
}
self.parts[partName] = part
end
local trackpadIndicator = CommonUtil.Create("Part") {
Parent = self.model,
Name = "TrackpadIndicator",
Material = Enum.Material.Neon,
BrickColor = BrickColor.new("Institutional white"),
Shape = Enum.PartType.Ball,
Anchored = false,
Transparency = 1,
Size = Vector3.new(0.05, 0.05, 0.05),
CanCollide = false
}
CommonUtil.Create("Weld") {
Parent = trackpadIndicator,
Name = "Weld",
Part0 = self.origin,
Part1 = trackpadIndicator,
C0 = PARTS_INFO.Trackpad.offset
}
self.parts.TrackpadIndicator = trackpadIndicator
self.model.PrimaryPart = self.origin
self:onTouchpadModeChanged(self.touchpad, VRService:GetTouchpadMode(self.touchpad))
return self
end
function ViveController:setPartVisible(partName, visible)
local part = self.parts[partName]
if part then
part.Transparency = visible and 0 or 1
end
end
function ViveController:setABXYEnabled(enabled)
self:setPartVisible("ButtonA", enabled)
self:setPartVisible("ButtonB", enabled)
self:setPartVisible("ButtonX", enabled)
self:setPartVisible("ButtonY", enabled)
end
function ViveController:setCFrame(cframe)
self.model:SetPrimaryPartCFrame(cframe)
end
function ViveController:setButtonState(partName, depressed)
local partInfo = PARTS_INFO[partName]
if not partInfo then return end
local offset = partInfo.offset
local moveOffset = partInfo.moveOffset
if offset and moveOffset then
local part = self.parts[partName]
if part then
local mesh = part:FindFirstChild("Mesh")
local weld = part:FindFirstChild("Weld")
if weld then
if depressed then
part.CFrame = self.origin.CFrame * offset * moveOffset
weld.C0 = offset * moveOffset
else
part.CFrame = self.origin.CFrame * offset
weld.C0 = offset
end
end
if mesh then
if depressed then
mesh.VertexColor = Vector3.new(0.5, 0.5, 0.5)
else
mesh.VertexColor = Vector3.new(1, 1, 1)
end
end
end
end
end
function ViveController:setTriggerState(state)
local partInfo = PARTS_INFO.Trigger
local offset = partInfo.offset
local part = self.parts.Trigger
local weld = part:FindFirstChild("Weld")
if weld then
local angleMin, angleMax = math.rad(0), math.rad(-20)
local angleRange = angleMax - angleMin
local rotCenter = Vector3.new(0, 0.05, -0.025)
local angle = (state * angleRange) + angleMin
local newOffset = offset * CFrame.new(rotCenter) * CFrame.Angles(angle, 0, 0) * CFrame.new(-rotCenter)
part.CFrame = self.origin.CFrame * newOffset
weld.C0 = newOffset
end
end
function ViveController:setTrackpadState(pos)
local part = self.parts.TrackpadIndicator
local weld = part:FindFirstChild("Weld")
if weld then
local pos3d = Vector3.new(pos.X, 0, -pos.Y) * 0.055
local trackpadSpace = CFrame.Angles(math.rad(6.5), 0, 0) * CFrame.new(0, 0.002 * (pos.magnitude ^ 3), 0)
local newOffset = PARTS_INFO.Trackpad.offset * CFrame.new(0, 0.01, 0) * trackpadSpace:toWorldSpace(CFrame.new(pos3d))
part.CFrame = self.origin.CFrame * newOffset
weld.C0 = newOffset
end
end
function ViveController:onButtonInputChanged(inputObject, depressed)
--Trackpad is bound to L3 or R3 depending on controller
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.ButtonR3) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.ButtonL3) then
self:setButtonState("Trackpad", depressed)
return
end
--Grips are bound to L2 or R2 depending on controller
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.ButtonR1) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.ButtonL1) then
self:setButtonState("LeftGrip", depressed)
self:setButtonState("RightGrip", depressed)
return
end
--ButtonStart on the right, ButtonSelect on the left
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.ButtonStart) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.ButtonSelect) then
self:setButtonState("MenuButton", depressed)
return
end
--If the touchpad mode is ABXY, then we have four buttons to deal with
if self.touchpadMode == Enum.VRTouchpadMode.ABXY then
if inputObject.KeyCode == Enum.KeyCode.ButtonA then
self:setButtonState("ButtonA", depressed)
return
end
if inputObject.KeyCode == Enum.KeyCode.ButtonB then
self:setButtonState("ButtonB", depressed)
return
end
if inputObject.KeyCode == Enum.KeyCode.ButtonX then
self:setButtonState("ButtonX", depressed)
return
end
if inputObject.KeyCode == Enum.KeyCode.ButtonY then
self:setButtonState("ButtonY", depressed)
return
end
end
end
function ViveController:onInputBegan(inputObject)
self:onButtonInputChanged(inputObject, true)
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick2) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick1) then
self:setPartVisible("TrackpadIndicator", true)
end
end
function ViveController:onInputChanged(inputObject)
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.ButtonR2) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.ButtonL2) then
self:setTriggerState(inputObject.Position.Z)
end
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick2) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick1) then
self:setTrackpadState(inputObject.Position)
end
end
function ViveController:onInputEnded(inputObject)
self:onButtonInputChanged(inputObject, false)
if (self.userCFrame == Enum.UserCFrame.RightHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick2) or
(self.userCFrame == Enum.UserCFrame.LeftHand and inputObject.KeyCode == Enum.KeyCode.Thumbstick1) then
self:setPartVisible("TrackpadIndicator", false)
end
end
function ViveController:onTouchpadModeChanged(touchpad, touchpadMode)
if touchpad ~= self.touchpad then
return
end
self.touchpadMode = touchpadMode
if touchpadMode == Enum.VRTouchpadMode.ABXY then
self:setABXYEnabled(true)
else
self:setABXYEnabled(false)
end
end
function ViveController:destroy()
if self.onTouchpadModeChangedConn then
self.onTouchpadModeChangedConn:disconnect()
self.onTouchpadModeChangedConn = nil
end
self.model:Destroy()
end
return ViveController
@@ -0,0 +1,199 @@
--Dialog: 3D dialogs for ROBLOX in VR
--written by 0xBAADF00D
--6/30/2016
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local InputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Utility = require(RobloxGui.Modules.Settings.Utility)
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local DIALOG_BG_COLOR = Color3.new(0.2, 0.2, 0.2)
local DIALOG_BG_TRANSPARENCY = 0.3
local DIALOG_TITLE_HEIGHT = 66
local DIALOG_COLOR_HEIGHT = 8
local DIALOG_TITLE_TEXT_SIZE = Enum.FontSize.Size36
local DIALOG_CONTENT_PADDING = 48
local TITLE_COLOR = Color3.new(1, 1, 1)
local PANEL_OFFSET_CF = CFrame.new(0, 0, -7) * CFrame.Angles(0, math.pi, 0)
local emptySelectionImage = Utility:Create "ImageLabel" {
Name = "EmptySelectionImage",
Image = "",
BackgroundTransparency = 1,
ImageTransparency = 1
}
local DialogPanel = Panel3D.Get("Dialog")
DialogPanel:SetType(Panel3D.Type.Fixed)
DialogPanel.localCF = PANEL_OFFSET_CF
DialogPanel:SetCanFade(true)
local dialogPanelAngle = 0
local opacityLookup = {}
local resetBaseAngle = false
local baseAngle = 0
local PANEL_FADE_ANGLE_0, PANEL_FADE_ANGLE_1 = math.rad(37.5), math.rad(44)
local PANEL_FADE_RANGE = PANEL_FADE_ANGLE_1 - PANEL_FADE_ANGLE_0
local PANEL_REAPPEAR_ANGLE = math.rad(90)
local function positionDialogPanel(desiredAngle)
dialogPanelAngle = desiredAngle
local headCF = InputService:GetUserCFrame(Enum.UserCFrame.Head)
local headPos = headCF.p
DialogPanel.localCF = CFrame.new(headPos) * CFrame.Angles(0, desiredAngle, 0) * PANEL_OFFSET_CF
end
function DialogPanel:CalculateTransparency()
local headCF = InputService:GetUserCFrame(Enum.UserCFrame.Head)
local headLook = headCF.lookVector * Vector3.new(1, 0, 1)
local vertAngle = math.asin(headCF.lookVector.Y)
local vectorToPanel = Vector3.new(math.cos(baseAngle + dialogPanelAngle + math.rad(90)), 0, -math.sin(baseAngle + dialogPanelAngle + math.rad(90)))
if math.abs(vertAngle) > PANEL_FADE_ANGLE_1 then
resetBaseAngle = true
end
local angleToPanel = math.acos(headLook:Dot(vectorToPanel))
return math.min(math.max(0, (angleToPanel - PANEL_FADE_ANGLE_0) / PANEL_FADE_RANGE), 1)
end
local function updatePanelPosition()
local headCF = InputService:GetUserCFrame(Enum.UserCFrame.Head)
local headLook = headCF.lookVector * Vector3.new(1, 0, 1)
local headAngle = (math.atan2(-headLook.Z, headLook.X) - math.rad(90)) % math.rad(360)
local newPanelAngle = baseAngle + math.floor((headAngle / PANEL_REAPPEAR_ANGLE) + 0.5) * PANEL_REAPPEAR_ANGLE
if resetBaseAngle then
resetBaseAngle = false
baseAngle = headAngle
end
positionDialogPanel(newPanelAngle)
end
-- RenderStep update function for the DialogPanel
function dialogPanelRenderUpdate()
if DialogPanel.transparency == 1 or resetBaseAngle then
updatePanelPosition()
end
--update the transparency of gui elements
local opacityMult = 1 - DialogPanel.transparency
for guiElement, baseOpacity in pairs(opacityLookup) do
local transparency = 1 - (baseOpacity * opacityMult)
if guiElement:IsA("TextLabel") or guiElement:IsA("TextButton") then
guiElement.TextTransparency = transparency
elseif guiElement:IsA("ImageLabel") or guiElement:IsA("ImageButton") then
guiElement.ImageTransparency = transparency
elseif guiElement:IsA("Frame") then
guiElement.BackgroundTransparency = transparency
end
end
end
local DialogQueue = {}
local currentDescendantConn = nil
local lastDialogShown = nil
local function updatePanel()
local currentDialog = DialogQueue[1]
if lastDialogShown and lastDialogShown.content then
lastDialogShown.content.Parent = nil
end
if not currentDialog or not currentDialog.content then
DialogPanel:SetVisible(false)
opacityLookup = {}
if currentDescendantConn then
currentDescendantConn:disconnect()
currentDescendantConn = nil
end
else
currentDialog.content.Parent = DialogPanel:GetGUI()
lastDialogShown = currentDialog
local contentSize = currentDialog.content.AbsoluteSize
DialogPanel:ResizePixels(contentSize.X, contentSize.Y, 100)
resetBaseAngle = true
updatePanelPosition()
DialogPanel:SetVisible(true)
opacityLookup = {}
local function search(parent)
if parent:IsA("ImageLabel") or parent:IsA("ImageButton") then
opacityLookup[parent] = 1 - parent.ImageTransparency
elseif parent:IsA("TextLabel") or parent:IsA("TextButton") then
opacityLookup[parent] = 1 - parent.TextTransparency
elseif parent:IsA("Frame") then
opacityLookup[parent] = 1 - parent.BackgroundTransparency
end
for i, v in pairs(parent:GetChildren()) do
search(v)
end
end
search(DialogPanel:GetGUI())
if currentDescendantConn then
currentDescendantConn:disconnect()
currentDescendantConn = nil
end
currentDescendantConn = DialogPanel:GetGUI().DescendantAdded:connect(function(descendant)
search(descendant)
end)
end
end
local Dialog = {}
Dialog.__index = Dialog
function Dialog.new(content)
local self = setmetatable({}, Dialog)
self.content = content
return self
end
function Dialog:SetContent(guiElement)
if not guiElement and self.content then
self.content.Parent = nil
end
self.content = guiElement
end
local renderFuncBound = false
function Dialog:Show(shouldTakeover)
if not renderFuncBound then
renderFuncBound = true
RunService:BindToRenderStep("DialogPanel", Enum.RenderPriority.First.Value, dialogPanelRenderUpdate)
end
if shouldTakeover then
table.insert(DialogQueue, 1, self)
else
table.insert(DialogQueue, self)
end
updatePanel()
end
function Dialog:Close()
for idx, dialog in pairs(DialogQueue) do
if dialog == self then
table.remove(DialogQueue, idx)
break
end
end
if renderFuncBound and #DialogQueue == 0 then
renderFuncBound = false
RunService:UnbindFromRenderStep("DialogPanel")
end
updatePanel()
end
return Dialog
@@ -0,0 +1,291 @@
-- Written by SolarCrane
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 MIN_COLOR_POSITION = 0.1
local MIN_COLOR = HEALTH_RED_COLOR
local MID_COLOR_POSITION = 0.5
local MAX_COLOR_POSITION = 0.8
local MAX_COLOR = HEALTH_GREEN_COLOR
local NAME_SPACE = 14
local PlayersService = game:GetService('Players')
local CoreGui = game:GetService('CoreGui')
local StarterGui = game:GetService('StarterGui')
local UserInputService = game:GetService('UserInputService')
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local healthbarContainer = Instance.new('Frame')
healthbarContainer.Name = 'HealthbarContainer'
healthbarContainer.BackgroundTransparency = 1
healthbarContainer.Size = UDim2.new(1,0,1,0)
local healthbarBack = Instance.new('ImageLabel')
healthbarBack.ImageColor3 = HEALTH_BACKGROUND_COLOR
healthbarBack.BackgroundTransparency = 1
healthbarBack.ScaleType = Enum.ScaleType.Slice
healthbarBack.SliceCenter = Rect.new(10, 10, 10, 10)
healthbarBack.Name = 'HealthbarBack'
healthbarBack.Image = 'rbxasset://textures/ui/VR/rectBackgroundWhite.png'
healthbarBack.Size = UDim2.new(1,0,0.3,0)
healthbarBack.Position = UDim2.new(0,0,0.7,0)
healthbarBack.Parent = healthbarContainer
local healthbarFront = Instance.new('ImageLabel')
healthbarFront.ImageColor3 = HEALTH_GREEN_COLOR
healthbarFront.BackgroundTransparency = 1
healthbarFront.ScaleType = Enum.ScaleType.Slice
healthbarFront.SliceCenter = Rect.new(10, 10, 10, 10)
healthbarFront.Size = UDim2.new(1, 0, 1, 0)
healthbarFront.Position = UDim2.new(0, 0, 0, 0)
healthbarFront.Name = 'HealthbarFill'
healthbarFront.Image = 'rbxasset://textures/ui/VR/rectBackgroundWhite.png'
healthbarFront.Parent = healthbarBack
local playerName = Instance.new('TextLabel')
playerName.Name = 'PlayerName'
playerName.BackgroundTransparency = 1
playerName.TextColor3 = Color3.new(1, 1, 1)
playerName.Text = ''
playerName.Font = Enum.Font.SourceSansBold
playerName.FontSize = Enum.FontSize.Size24
playerName.TextXAlignment = Enum.TextXAlignment.Left
playerName.Size = UDim2.new(1, 0, 0, NAME_SPACE)
playerName.Parent = healthbarContainer
local function Color3ToVec3(color)
return Vector3.new(color.r, color.g, color.b)
end
local function FindChildOfType(object, className)
for _, child in pairs(object:GetChildren()) do
if child:IsA(className) then
return child
end
end
end
local HEALTH_COLOR_TO_POSITION = {
[Color3ToVec3(MIN_COLOR)] = MIN_COLOR_POSITION;
[Color3ToVec3(HEALTH_YELLOW_COLOR)] = MID_COLOR_POSITION;
[Color3ToVec3(MAX_COLOR)] = MAX_COLOR_POSITION;
}
local function HealthbarColorTransferFunction(healthPercent)
if healthPercent <= MIN_COLOR_POSITION then
return MIN_COLOR
elseif healthPercent >= MAX_COLOR_POSITION then
return MAX_COLOR
end
-- Shepard's Interpolation
local numeratorSum = Vector3.new(0,0,0)
local denominatorSum = 0
for colorSampleValue, samplePoint in pairs(HEALTH_COLOR_TO_POSITION) 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 function UpdateHealth(humanoid)
local percentHealth = humanoid.Health / humanoid.MaxHealth
if percentHealth ~= percentHealth then
percentHealth = 1
end
healthbarFront.ImageColor3 = HealthbarColorTransferFunction(percentHealth)
healthbarFront.Size = UDim2.new(percentHealth, 0, 1, 0)
end
local HumanoidChangedConn = nil
local HumanoidAncestryChangedConn = nil
local function RegisterHumanoid(humanoid)
if HumanoidAncestryChangedConn then
HumanoidAncestryChangedConn:disconnect()
HumanoidAncestryChangedConn = nil
end
if HumanoidChangedConn then
HumanoidChangedConn:disconnect()
HumanoidChangedConn = nil
end
if humanoid then
HumanoidAncestryChangedConn = humanoid.AncestryChanged:connect(function(child, parent)
local player = PlayersService.LocalPlayer
if child == humanoid and (not player or parent ~= player.Character) then
RegisterHumanoid(nil)
end
end)
HumanoidChangedConn = humanoid.HealthChanged:connect(function() UpdateHealth(humanoid) end)
UpdateHealth(humanoid)
end
end
local function OnCharacterChildAdded(child)
local player = PlayersService.LocalPlayer
if player and child.Parent == player.Character and child:IsA('Humanoid') then
RegisterHumanoid(child)
end
end
local CharacterChildAddedConn = nil
local function OnCharacterAdded(character)
local humanoid = FindChildOfType(character, 'Humanoid')
if humanoid then
RegisterHumanoid(humanoid)
end
if CharacterChildAddedConn then
CharacterChildAddedConn:disconnect()
CharacterChildAddedConn = nil
end
CharacterChildAddedConn = character.ChildAdded:connect(OnCharacterChildAdded)
end
local function OnPlayerAdded(player)
playerName.Text = player.Name
player.CharacterAdded:connect(OnCharacterAdded)
if player.Character then
OnCharacterAdded(player.Character)
end
end
if PlayersService.LocalPlayer then
OnPlayerAdded(PlayersService.LocalPlayer)
else
spawn(function()
while not PlayersService.LocalPlayer do
PlayersService.ChildAdded:wait()
end
OnPlayerAdded(PlayersService.LocalPlayer)
end)
end
local Healthbar = {}
Healthbar.ModuleName = "Healthbar"
Healthbar.KeepVRTopbarOpen = false
Healthbar.VRIsExclusive = false
Healthbar.VRClosesNonExclusive = false
local CoreGuiChangedConn, VRModuleOpenedConn, VRModuleClosedConn;
local function OnVREnabled(prop)
if prop == "VREnabled" then
if UserInputService.VREnabled then
local VRHub = require(RobloxGui.Modules.VR.VRHub)
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local HealthbarPanel = Panel3D.Get("Healthbar")
HealthbarPanel:ResizeStuds(1.5, 0.25, 128)
HealthbarPanel:SetType(Panel3D.Type.Fixed, { CFrame = CFrame.new(0, 0, -5) })
HealthbarPanel:SetVisible(true)
function HealthbarPanel:PreUpdate(cameraCF, cameraRenderCF, userHeadCF, lookRay)
local relativePanel = Panel3D.Get("Backpack") or Panel3D.Get("Topbar3D")
local topbarPanel = Panel3D.Get("Topbar3D")
if relativePanel and topbarPanel then
local panelOriginCF = relativePanel.localCF or CFrame.new()
-- Line up the Healthbar with the backpack icons, which are set 0.11 inwards
self.localCF = panelOriginCF * CFrame.new(math.max(topbarPanel.width, relativePanel.width)/2 - HealthbarPanel.width/2 - 0.11, 0.25, 0.1)
end
end
function HealthbarPanel:CalculateTransparency()
local backpackPanel = Panel3D.Get("Backpack")
local topbarPanel = Panel3D.Get("Topbar3D")
local transparency = math.min(
backpackPanel and backpackPanel:IsVisible() and backpackPanel:CalculateTransparency() or 1,
topbarPanel and topbarPanel:IsVisible() and topbarPanel:CalculateTransparency() or 1)
healthbarBack.ImageTransparency = transparency
healthbarFront.ImageTransparency = transparency
playerName.TextTransparency = transparency
return transparency
end
local OtherPanelOpen = false
local function UpdateExclusivePanelOpen()
for _, openModule in pairs(VRHub:GetOpenedModules()) do
if openModule.VRIsExclusive then
OtherPanelOpen = true
return
end
end
OtherPanelOpen = false
end
local function CalculateVisibility()
return StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.Health) and
not OtherPanelOpen
end
CoreGuiChangedConn = StarterGui.CoreGuiChangedSignal:connect(function()
HealthbarPanel:SetVisible(CalculateVisibility())
end)
VRModuleOpenedConn = VRHub.ModuleOpened.Event:connect(function(moduleName)
UpdateExclusivePanelOpen()
HealthbarPanel:SetVisible(CalculateVisibility())
end)
VRModuleClosedConn = VRHub.ModuleClosed.Event:connect(function(moduleName)
UpdateExclusivePanelOpen()
HealthbarPanel:SetVisible(CalculateVisibility())
end)
-- Initialize OtherPanelOpen variable
UpdateExclusivePanelOpen()
HealthbarPanel:SetVisible(CalculateVisibility())
healthbarContainer.Parent = HealthbarPanel:GetGUI()
VRHub:RegisterModule(Healthbar)
else
if CoreGuiChangedConn then
CoreGuiChangedConn:disconnect()
CoreGuiChangedConn = nil
end
if VRModuleOpenedConn then
VRModuleOpenedConn:disconnect()
VRModuleOpenedConn = nil
end
if VRModuleClosedConn then
VRModuleClosedConn:disconnect()
VRModuleClosedConn = nil
end
healthbarContainer.Parent = nil
end
end
end
UserInputService.Changed:connect(OnVREnabled)
if UserInputService.VREnabled then
OnVREnabled("VREnabled")
end
return Healthbar
@@ -0,0 +1,670 @@
--!nocheck
--LaserPointer.lua
--Implements the visual part of the VR laser pointer
--Written by Kyle, September 2016
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local GuiService = game:GetService("GuiService")
local VRService = game:GetService("VRService")
local Utility = require(RobloxGui.Modules.Settings.Utility)
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.Changed:wait()
LocalPlayer = Players.LocalPlayer
end
local gamma, invGamma = 2.2, 1/2.2
local function fromLinearRGB(color)
return Color3.new(color.r ^ gamma, color.g ^ gamma, color.b ^ gamma)
end
local function toLinearRGB(color)
return Color3.new(color.r ^ invGamma, color.g ^ invGamma, color.b ^ invGamma)
end
local function addPartsToGame(...)
local parts = {...}
local container = GuiService.CoreEffectFolder
--The container might not be ready yet.
if not container then
coroutine.wrap(function()
--Wait until the container is ready, then add the parts to it.
while GuiService.Changed:wait() ~= "CoreEffectFolder" and GuiService.CoreEffectFolder == nil do end
for _, part in pairs(parts) do
part.Parent = container
end
end)()
else
--The container is ready, no waiting necessary.
for _, part in pairs(parts) do
part.Parent = container
end
end
end
local function removePartsFromGame(...)
local parts = {...}
for _, part in pairs(parts) do
part.Parent = nil
end
end
local function getLocalHumanoid()
local character = LocalPlayer.Character
if not character then
return
end
for _, child in pairs(character:GetChildren()) do
if child:IsA("Humanoid") then
return child
end
end
end
local function applyExpCurve(x, exp)
local y = x ^ exp
if y ~= y then
y = math.abs(x) ^ exp
end
return y
end
local HEAD_MOUNT_OFFSET = Vector3.new(0.5, 0.5, 0)
local HEAD_MOUNT_THICKNESS_MULTIPLIER = 0.25
local BUTTON_LONG_PRESS_TIME = 0.75
local LaserPointerMode = {
Disabled = 0,
Pointer = 1,
Navigation = 2,
Hidden = 3
}
--Teleport visual configuration
local TELEPORT = {
MODE_ENABLED = true,
ARC_COLOR_GOOD = fromLinearRGB(Color3.fromRGB(0, 162, 255)),
ARC_COLOR_BAD = fromLinearRGB(Color3.fromRGB(253, 68, 72)),
ARC_THICKNESS = 0.025,
PLOP_GOOD = "rbxasset://textures/ui/VR/VRPointerDiscBlue.png",
PLOP_BAD = "rbxasset://textures/ui/VR/VRPointerDiscRed.png",
PLOP_BALL_COLOR_GOOD = BrickColor.new("Bright green"),
PLOP_BALL_COLOR_BAD = BrickColor.new("Bright red"),
PLOP_BALL_SIZE = 0.5,
PLOP_SIZE = 2,
PLOP_PULSE_MIN_SIZE = 0,
PLOP_PULSE_MAX_SIZE = 2,
MAX_VALID_DISTANCE = 100,
BUTTON_DOWN_THRESHOLD = 0.95,
BUTTON_UP_THRESHOLD = 0.5,
MIN_VELOCITY = 10,
RANGE_T_EXP = 2,
G = 10, -- Gravity constant for parabola
PULSE_DURATION = 0.8,
PULSE_PERIOD = 1,
PULSE_EXP = 2,
PULSE_SIZE_0 = 0.25,
PULSE_SIZE_1 = 2,
BALL_WAVE_PERIOD = 2,
BALL_WAVE_AMPLITUDE = 0.5,
BALL_WAVE_START = 0.25,
BALL_WAVE_EXP = 0.8,
FLOOR_OFFSET = 4.5,
FADE_OUT_DURATION = 0.125,
FADE_IN_DURATION = 0.125,
TRANSITION_DURATION = 0.25,
TRANSITION_FUNC = Utility:GetEaseInOutQuad(),
}
local LASER = {
MODE_ENABLED = true,
ARC_COLOR_GOOD = TELEPORT.ARC_COLOR_GOOD,
ARC_COLOR_BAD = TELEPORT.ARC_COLOR_BAD,
ARC_THICKNESS = 0.02,
MAX_DISTANCE = 500,
G = 0, -- Gravity constant for parabola; in this case we want a laser/straight line
--Couldn't figure out a good name for this. This is the maximum angle that the parabola's hit point
--can be from the laser's hit point when switching to laser pointer mode solely from the parabola hitting
--a gui part.
SWITCH_AIM_THRESHOLD = math.rad(15),
TRANSITION_DURATION = 0.075,
TRANSITION_FUNC = Utility:GetEaseInOutQuad()
}
local zeroVector2, identityVector2 = Vector2.new(0, 0), Vector2.new(1, 1)
local zeroVector3, identityVector3 = Vector3.new(0, 0, 0), Vector3.new(1, 1, 1)
local flattenMask = Vector3.new(1, 0, 1) --flattens a direction vector when multiplied by removing the vertical component
local minimumPartSize = Vector3.new(0.2, 0.2, 0.2)
local identity = CFrame.new()
local aimableStates = {
[Enum.HumanoidStateType.Running] = true,
[Enum.HumanoidStateType.RunningNoPhysics] = true,
[Enum.HumanoidStateType.None] = true
}
local LaserPointer = {}
LaserPointer.__index = LaserPointer
LaserPointer.Mode = LaserPointerMode
function LaserPointer.new()
local self = setmetatable({}, LaserPointer)
self.mode = LaserPointerMode.Disabled
self.lastMode = self.mode
self.inputUserCFrame = Enum.UserCFrame.RightHand
self.equippedTool = false
self.lastLaserModeHit = tick()
self.guiMenuIsOpen = false
self.externalForcePointer = false
self.navHitPoint = zeroVector3
self.navHitNormal = Vector3.new(0, 1, 0)
self.navHitPart = nil
self.navigationIsValid = false
self.lastNavigationValidityChangeTime = tick()
self.plopBallBounceStart = tick()
self.buttonPressStart = 0
do --Create the instances that make up the Laser Pointer
self.parabola = Utility:Create("ParabolaAdornment") {
Name = "LaserPointerParabola",
Parent = CoreGui,
A = -1,
B = 2,
C = 0,
Color3 = TELEPORT.COLOR_GOOD,
Thickness = TELEPORT.ARC_THICKNESS,
Visible = false
}
self.originPart = Utility:Create("Part") {
Name = "LaserPointerOrigin",
Anchored = true,
CanCollide = false,
TopSurface = Enum.SurfaceType.SmoothNoOutlines,
BottomSurface = Enum.SurfaceType.SmoothNoOutlines,
Material = Enum.Material.SmoothPlastic,
Size = minimumPartSize,
Transparency = 1
}
self.parabola.Adornee = self.originPart
self.plopPart = Utility:Create("Part") {
Name = "LaserPointerTeleportPlop",
Anchored = true,
CanCollide = false,
Size = minimumPartSize,
Transparency = 1
}
self.plopBall = Utility:Create("Part") {
Name = "LaserPointerTeleportPlopBall",
Anchored = true,
CanCollide = false,
TopSurface = Enum.SurfaceType.SmoothNoOutlines,
BottomSurface = Enum.SurfaceType.SmoothNoOutlines,
Material = Enum.Material.Neon,
BrickColor = TELEPORT.PLOP_BALL_COLOR_GOOD,
Shape = Enum.PartType.Ball,
Size = identityVector3 * TELEPORT.PLOP_BALL_SIZE
}
self.plopAdorn = Utility:Create("ImageHandleAdornment") {
Name = "LaserPointerTeleportPlopAdorn",
Parent = self.plopPart,
Adornee = self.plopPart,
Size = identityVector2 * TELEPORT.PLOP_SIZE,
Image = TELEPORT.PLOP_GOOD
}
self.plopAdornPulse = Utility:Create("ImageHandleAdornment") {
Name = "LaserPointerTeleportPlopAdornPulse",
Parent = self.plopPart,
Adornee = self.plopPart,
Size = zeroVector2,
Image = TELEPORT.PLOP_GOOD,
Transparency = 0.5
}
end
do --Event connections and final setup
GuiService.MenuOpened:connect(function()
self.guiMenuIsOpen = true
end)
GuiService.MenuClosed:connect(function()
self.guiMenuIsOpen = false
end)
self.inputUserCFrame = VRService.GuiInputUserCFrame
VRService.Changed:connect(function(prop)
if prop == "GuiInputUserCFrame" then
self.inputUserCFrame = VRService.GuiInputUserCFrame
end
end)
end
self:onModeChanged(self.mode)
self:updateInputUserCFrame()
return self
end
function LaserPointer.hasAnyHandController()
return VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) or
VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand)
end
function LaserPointer.getModeName(mode)
for name, value in pairs(LaserPointerMode) do
if mode == value then
return name
end
end
return "unknown"
end
function LaserPointer:updateInputUserCFrame()
if VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) then
VRService.GuiInputUserCFrame = Enum.UserCFrame.RightHand
elseif VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand) then
VRService.GuiInputUserCFrame = Enum.UserCFrame.LeftHand
else
VRService.GuiInputUserCFrame = Enum.UserCFrame.Head
end
end
function LaserPointer:onModeChanged(newMode)
self:updateInputUserCFrame()
--Disabled mode
if newMode == LaserPointerMode.Disabled or newMode == LaserPointerMode.Hidden then
removePartsFromGame(self.originPart, self.plopPart, self.plopBall)
self.parabola.Visible = false
self:setNavigationActionEnabled(false)
--Pointer mode
elseif newMode == LaserPointerMode.Pointer then
addPartsToGame(self.originPart)
removePartsFromGame(self.plopPart, self.plopBall)
self.parabola.Visible = true
self:setNavigationActionEnabled(false)
--Navigation mode
elseif newMode == LaserPointerMode.Navigation then
addPartsToGame(self.originPart, self.plopPart, self.plopBall)
self.parabola.Visible = true
self:setNavigationActionEnabled(true)
end
end
function LaserPointer:setMode(mode)
if mode == self.mode then
return
end
local oldMode = self.mode
self.mode = mode
self.lastMode = oldMode
self:onModeChanged(mode)
end
function LaserPointer:getMode()
return self.mode
end
local cosMax = math.cos(math.pi/4)
local sinMax = math.sin(math.pi/4)
function LaserPointer:calculateLaunchVelocity(gravity, desiredRange, height)
--return math.sqrt(desiredRange * gravity) / math.sqrt(math.sin(2*math.pi/4))
--This calculates a launch velocity for an imaginary projectile that will start at y=height, travel desiredRange
--in the x direction until it hits y=0. This is only reached when the projectile is launched at the optimal
--launch angle, 45 degrees elevation. Anything above or below that will fall short, which is desired.
--See https://en.wikipedia.org/wiki/Range_of_a_projectile for a breakdown of this function.
return -(math.sqrt(gravity / cosMax)*desiredRange) / (math.sqrt(2*height*cosMax+2*desiredRange*sinMax))
end
function LaserPointer:isHeadMounted()
return self.inputUserCFrame == Enum.UserCFrame.Head
end
function LaserPointer:shouldForcePointer()
return self.externalForcePointer or self.guiMenuIsOpen
end
function LaserPointer:setForcePointer(force)
self.externalForcePointer = force
end
function LaserPointer:getNavigationOrigin()
local humanoid = getLocalHumanoid()
if not humanoid then return end
local rootPart = humanoid.Torso
if not rootPart then return end
local hipHeight = humanoid.HipHeight
if humanoid.RigType == Enum.HumanoidRigType.R6 then
hipHeight = 2
end
local rootPartOffset = rootPart.Size.Y / 2
return rootPart.Position + Vector3.new(0, -rootPartOffset - hipHeight, 0)
end
function LaserPointer:horzDistanceFromCharacter(point)
local character = LocalPlayer.Character
if not character then
return math.huge
end
local rootPart = character:FindFirstChild("HumanoidRootPart")
if not rootPart then
return math.huge
end
return ((rootPart:GetRenderCFrame().p - point) * flattenMask).magnitude
end
function LaserPointer:onNavigateAction(actionName, inputState, inputObj)
if inputState == Enum.UserInputState.Begin then
VRService:RequestNavigation(CFrame.new(self.navHitPoint, self.navHitPoint + self.navHitNormal) * CFrame.Angles(math.pi/2, 0, 0), self.inputUserCFrame)
end
end
function LaserPointer:setNavigationActionEnabled(enabled)
if enabled then
ContextActionService:BindCoreAction("LaserPointerNavigate", function(...) self:onNavigateAction(...) end, false, Enum.KeyCode.ButtonA)
else
ContextActionService:UnbindCoreAction("LaserPointerNavigate")
end
end
function LaserPointer:setArcLaunchParams(launchAngle, launchVelocity, gravity, desiredRange)
local velocityX = math.cos(launchAngle) * launchVelocity
local velocityY = math.sin(launchAngle) * launchVelocity
--don't let velocityX = 0 or we get a divide-by-zero and bad things happen
if velocityX == 0 then
velocityX = 1e-6
end
self.parabola.A = -(0.5 * gravity) * (1 / (velocityX ^ 2))
self.parabola.B = velocityY / velocityX
self.parabola.C = 0
self.parabola.Range = desiredRange * 1.5
end
function LaserPointer:renderAsParabola(origin, lookDir)
local lookFlat = lookDir * flattenMask
self.originPart.CFrame = CFrame.new(origin, origin + lookFlat) * CFrame.Angles(0, math.pi / 2, 0)
end
function LaserPointer:renderAsLaser(laserOriginPos, laserEndpoint)
self.originPart.CFrame = CFrame.new(laserOriginPos, laserEndpoint) * CFrame.Angles(0, math.pi / 2, 0)
self.parabola.A = 0
self.parabola.B = 1e-6
self.parabola.C = 0
self.parabola.Range = (laserEndpoint - laserOriginPos).magnitude
end
function LaserPointer:getArcHit(pos, look, ignore)
if self.parabola.A == 0 then
--Just skip the parabola since this is effectively a line without the x^2 term
return self:getLaserHit(pos, look, ignore)
end
self:renderAsParabola(pos, look)
local parabHitPart, parabHitPoint, parabHitNormal, _, t = self.parabola:FindPartOnParabola(ignore)
return parabHitPart, parabHitPoint, parabHitNormal, t
end
function LaserPointer:getLaserHit(pos, look, ignore)
local ray = Ray.new(pos, look * LASER.MAX_DISTANCE)
local laserHitPart, laserHitPoint, laserHitNormal, laserHitMaterial = workspace:FindPartOnRayWithIgnoreList(ray, ignore)
local t = (laserHitPoint - pos).magnitude / LASER.MAX_DISTANCE
return laserHitPart, laserHitPoint, laserHitNormal, t
end
function LaserPointer:canNavigateTo(part, point, normal)
local character = LocalPlayer.Character
if not character then
return false
end
local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
if not humanoidRootPart then
return false
end
--Check if a part was hit
if not part then
return false
end
--Check if the surface hit is upside down or not
if normal.Y < -1e-6 then
return false
end
return true
end
function LaserPointer:checkHeadMountedMode(laserHitPart)
if self:shouldForcePointer() then
self:setMode(LaserPointerMode.Pointer)
return
end
local coreGuiPartContainer = GuiService.CoreGuiFolder
if laserHitPart and laserHitPart:IsDescendantOf(coreGuiPartContainer) then
self:setMode(LaserPointerMode.Pointer)
else
self:setMode(LaserPointerMode.Navigation)
end
end
function LaserPointer:checkMode(originPos, parabHitPart, parabHitPoint, laserHitPart, laserHitPoint)
if self:shouldForcePointer() then
self:setMode(LaserPointerMode.Pointer)
return
end
local angleBetween = 0
--only check the angle between these two if the hit points aren't exactly equal
if parabHitPoint ~= laserHitPoint then
local toParabHit = (parabHitPoint - originPos).unit
local toLaserHit = (laserHitPoint - originPos).unit
angleBetween = math.acos(toParabHit:Dot(toLaserHit))
end
--todo: update this when we move the parts; also update it so that it can work with user surfaceguis
--we may need to be more creative about that since we can't easily tell if a part has a surfacegui from Lua
local coreGuiPartContainer = GuiService.CoreGuiFolder
local laserHitGui = laserHitPart and laserHitPart:IsDescendantOf(coreGuiPartContainer)
local parabHitGui = laserHitGui
--only check parab hit part if it's not the same as the laser hit part
if parabHitPart ~= laserHitPart then
parabHitGui = parabHitPart and parabHitPart:IsDescendantOf(coreGuiPartContainer)
end
local newMode = self.mode
if laserHitGui then
self.lastLaserModeHit = tick()
end
--If we are navigating and the parabola hits a gui part, we switch to laser pointer if the laser pointer is close enough
--If we are navigating and the laser hits a gui part, we switch to laser pointer regardless of where the parabola is
if self.mode ~= LaserPointerMode.Pointer and laserHitGui then
if self:isHeadMounted() or ((parabHitGui and angleBetween < LASER.SWITCH_AIM_THRESHOLD) or laserHitGui) then
newMode = LaserPointerMode.Pointer
end
end
--If we are in laser pointer mode but neither the parabola nor the laser hit any gui parts, we switch back to navigation or hidden mode.
if self.mode == LaserPointerMode.Pointer and not laserHitGui and not parabHitGui and tick() - self.lastLaserModeHit > 0.2 then
if self.lastMode == LaserPointerMode.Navigation or self.lastMode == LaserPointerMode.Hidden then
newMode = self.lastMode
end
end
self:setMode(newMode)
end
function LaserPointer:setNavigationValidState(valid)
if valid == self.navigationIsValid then return end
self.navigationIsValid = valid
self.lastNavigationValidityChangeTime = tick()
if valid then
self.plopBallBounceStart = tick()
self.parabola.Color3 = TELEPORT.ARC_COLOR_GOOD
self.plopAdorn.Visible = true
self.plopAdorn.Image = TELEPORT.PLOP_GOOD
self.plopAdornPulse.Visible = true
self.plopAdornPulse.Image = TELEPORT.PLOP_GOOD
self.plopBall.BrickColor = TELEPORT.PLOP_BALL_COLOR_GOOD
else
self.parabola.Color3 = TELEPORT.ARC_COLOR_BAD
self.plopAdorn.Visible = false
self.plopAdorn.Image = TELEPORT.PLOP_BAD
self.plopAdornPulse.Visible = false
self.plopAdornPulse.Image = TELEPORT.PLOP_BAD
self.plopBall.BrickColor = TELEPORT.PLOP_BALL_COLOR_BAD
end
end
function LaserPointer:updateNavPlop(parabHitPoint, parabHitNormal)
local now = tick() - self.plopBallBounceStart
local plopCF = CFrame.new(parabHitPoint, parabHitPoint + parabHitNormal)
local ballHeight = 0
if self.navigationIsValid then
local ballWave = applyExpCurve(math.sin((now * 2 * math.pi) / TELEPORT.BALL_WAVE_PERIOD), TELEPORT.BALL_WAVE_EXP)
ballHeight = TELEPORT.BALL_WAVE_START + (ballWave * TELEPORT.BALL_WAVE_AMPLITUDE)
end
self.plopPart.CFrame = plopCF
self.plopBall.CFrame = plopCF * CFrame.new(0, 0, -ballHeight)
--Handle the pulse animation
--We're scheduling it to begin every TELEPORT.PULSE_PERIOD seconds
--and the animation runs for TELEPORT.PULSE_DURATION seconds. TELEPORT.PULSE_EXP
--affects the growth rate of the pulse size; ^2 is a good look, starts slow and accelerates.
local timeSincePulseStart = now % TELEPORT.PULSE_PERIOD
if timeSincePulseStart > 0 then
local pulseSize = timeSincePulseStart / TELEPORT.PULSE_DURATION
if pulseSize < 1 then
self.plopAdornPulse.Visible = true
self.plopAdornPulse.Size = identityVector2 * (TELEPORT.PULSE_SIZE_0 + applyExpCurve(pulseSize, TELEPORT.PULSE_EXP) * (TELEPORT.PULSE_SIZE_1 - TELEPORT.PULSE_SIZE_0))
self.plopAdornPulse.Transparency = 0.5 + (pulseSize * 0.5)
else
self.plopAdornPulse.Visible = false
self.plopAdornPulse.Size = zeroVector2
self.pulseStartTime = tick() + TELEPORT.PULSE_PERIOD
end
end
end
function LaserPointer:updateNavigationMode(hitPoint, hitNormal, hitPart)
self.navHitPoint = hitPoint
self.navHitNormal = hitNormal
self.navHitPart = hitPart
self:updateNavPlop(hitPoint, hitNormal)
self:setNavigationValidState(self:canNavigateTo(self.navHitPart, self.navHitPoint, self.navHitNormal))
end
function LaserPointer:update(dt)
if self.mode == LaserPointerMode.Disabled then
return
end
local humanoid = getLocalHumanoid()
local ignore = { LocalPlayer.Character or LocalPlayer, self.originPart, self.plopPart, self.plopBall, GuiService.CoreEffectFolder }
local cameraSpace = workspace.CurrentCamera.CFrame
local thickness0, thickness1 = LASER.ARC_THICKNESS, TELEPORT.ARC_THICKNESS
local gravity0, gravity1 = LASER.G, TELEPORT.G
if self:isHeadMounted() then
self.parabola.Thickness = LASER.ARC_THICKNESS * HEAD_MOUNT_THICKNESS_MULTIPLIER
--cast ray from center of camera, then render laser going from offset point to hit point
local originCFrame = cameraSpace * VRService:GetUserCFrame(Enum.UserCFrame.Head)
local originPos, originLook = originCFrame.p, originCFrame.lookVector
local laserHitPart, laserHitPoint, laserHitNormal, laserHitT = self:getLaserHit(originPos, originLook, ignore)
self:checkHeadMountedMode(laserHitPart)
--we actually want to render the laser from an offset from the head though
local offsetPosition = originCFrame:pointToWorldSpace(HEAD_MOUNT_OFFSET * workspace.CurrentCamera.HeadScale)
self:renderAsLaser(offsetPosition, laserHitPoint)
if self.mode == LaserPointerMode.Navigation then
self:updateNavigationMode(laserHitPoint, laserHitNormal, laserHitPart, laserHitPoint)
else
self.parabola.Color3 = LASER.ARC_COLOR_GOOD
end
else
local originCFrame = cameraSpace * VRService:GetUserCFrame(self.inputUserCFrame)
local originPos, originLook = originCFrame.p, originCFrame.lookVector
local gravity = TELEPORT.G
local launchAngle = math.asin(originLook.Y)
local offsetHeight = humanoid and originPos.Y - self:getNavigationOrigin().Y or 0
local desiredRange = TELEPORT.MAX_VALID_DISTANCE
local launchVelocity = self:calculateLaunchVelocity(gravity, desiredRange, offsetHeight)
self:setArcLaunchParams(launchAngle, launchVelocity, gravity, desiredRange)
--Always check for both parabola and laser hits so we can use it to judge when to transition
ignore[6] = GuiService.CoreGuiFolder
local parabHitPart, parabHitPoint, parabHitNormal, parabHitT = self:getArcHit(originPos, originLook, ignore)
--Clear the gui folder out of our ignore table and cast so we might hit SurfaceGuis with the laser
ignore[6] = nil
local laserHitPart, laserHitPoint, laserHitNormal, laserHitT = self:getLaserHit(originPos, originLook, ignore)
self:checkMode(originPos, parabHitPart, parabHitPoint, laserHitPart, laserHitPoint)
if self.mode == LaserPointerMode.Navigation then
self.parabola.Range = self.parabola.Range * parabHitT
self.parabola.Thickness = TELEPORT.ARC_THICKNESS
self:updateNavigationMode(parabHitPoint, parabHitNormal, parabHitPart)
else
self.parabola.Color3 = LASER.ARC_COLOR_GOOD
self.parabola.Thickness = LASER.ARC_THICKNESS
self:renderAsLaser(originPos, laserHitPoint)
end
end
end
return LaserPointer
@@ -0,0 +1,791 @@
--!nocheck
local ContextActionService = game:GetService("ContextActionService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Util = require(RobloxGui.Modules.Settings.Utility)
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local VRHub = require(RobloxGui.Modules.VR.VRHub)
local PANEL_OFFSET_CFRAME = CFrame.Angles(math.rad(-5), 0, 0) * CFrame.new(0, 4, 0) * CFrame.Angles(math.rad(-15), 0, 0)
local NO_TRANSITION_ANIMATIONS = false
local ANIMATE_OUT_DISTANCE = -100
local ANIMATE_OUT_DURATION = 0.25
local PIXELS_PER_STUD = 150
local WINDOW_TITLEBAR_HEIGHT = 72
local BLURRED_TITLEBAR_COLOR = Color3.new(78 / 255, 84 / 255, 96 / 255)
local FOCUSED_TITLEBAR_COLOR = Color3.new(82 / 255, 101 / 255, 141 / 255)
local WINDOW_BG_COLOR = Color3.new(20/255, 20/255, 20/255)
local WINDOW_BG_TRANSPARENCY = 0.5
local POPOUT_DISTANCE = 0.25
local POPOUT_DURATION = 0.25
local NOTIFICATION_WIDTH_SCALE = 0.85
local NOTIFICATION_HEIGHT_OFFSET = 80
local NOTIFICATION_PADDING_Y = 20
local NOTIFICATION_PADDING_X_SCALE = (1 - NOTIFICATION_WIDTH_SCALE) / 2
local NOTIFICATION_DEPTH_OFFSET = 0.25
local NOTIFICATION_BG_COLOR = Color3.new(0.2, 0.2, 0.2)
local NOTIFICATION_BG_TRANSPARENCY = 0.1
local MAX_NOTIFICATIONS_SHOWN = 3
local MAX_DETAILS_SHOWN = 2
local DETAILS_PADDING = 20
local BUTTON_1_POS = 0.07
local BUTTON_2_POS = 0.511
local BUTTON_SINGLE_SIZE = 0.86
local BUTTON_DOUBLE_SIZE = 0.415
local BUTTON_Y_POS = 0.55
local BUTTON_Y_SIZE = 0.29
local BUTTON_NORMAL_IMG = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButton.png"
local BUTTON_SELECTED_IMG = "rbxasset://textures/ui/Settings/MenuBarAssets/MenuButtonSelected.png"
local CLOSE_BUTTON_IMG = "rbxasset://textures/ui/Keyboard/close_button_icon.png"
local CLOSE_BUTTON_HOVER = "rbxasset://textures/ui/Keyboard/close_button_selection.png"
local CLOSE_BUTTON_SIZE = 32
local CLOSE_BUTTON_OFFSET = 22
local CLOSE_BUTTON_HOVER_OFFSET = 22
local emptySelectionImage = Util:Create "ImageLabel" {
BackgroundTransparency = 1,
Image = ""
}
local AVATAR_IMAGE_URL = 'http://www.roblox.com/thumbs/avatar.ashx?userId=%d&x=%d&y=%d'
local TEXT_COLOR = Color3.new(1, 1, 1)
local aspectRatio = 1.62666666
local totalHeight = 3.5
local totalWidth = totalHeight * aspectRatio
local leftPanelWidth = totalWidth * 0.4
local rightPanelWidth = totalWidth * 0.6
local panelOffset = -totalWidth / 2
local leftOffset = (panelOffset + (leftPanelWidth * 0.5))
local rightOffset = (leftOffset + (leftPanelWidth * 0.5) + (rightPanelWidth * 0.5))
local NotificationHubModule = {}
NotificationHubModule.ModuleName = "Notifications"
NotificationHubModule.KeepVRTopbarOpen = true
NotificationHubModule.VRIsExclusive = true
NotificationHubModule.VRClosesNonExclusive = true
NotificationHubModule.UnreadCountChanged = function() end
VRHub:RegisterModule(NotificationHubModule)
local notificationsPanel = Panel3D.Get("Notifications")
local notificationsWindow = nil
local detailsPanel = Panel3D.Get("NotificationDetails")
local detailsWindow = nil
local function IsDeveloperGroupEnabled()
return false
end
local WindowFrame = {}
do
local windows = {}
local WindowFrame_mt = { __index = WindowFrame }
function WindowFrame.new(panel, parent, title)
local instance = {}
table.insert(windows, instance)
instance.zeroCF = panel.localCF
instance.zOffset = 0
instance.isPopping = false
instance.isAnimating = false
instance.tweener = nil
instance.panel = panel
instance.panel.OnMouseEnter = function()
for i, v in pairs(windows) do
if v ~= instance then
v:SetPopOut(false)
end
end
instance:SetPopOut(true)
end
instance.titlebar = Util:Create "ImageLabel" {
Parent = parent,
Name = "TitlebarBackground",
Position = UDim2.new(0, -1, 0, -1),
Size = UDim2.new(1, 2, 0, WINDOW_TITLEBAR_HEIGHT + 2),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png",
ImageColor3 = BLURRED_TITLEBAR_COLOR,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(10, 10, 10, 10)
}
instance.titleText = Util:Create "TextLabel" {
Parent = instance.titlebar,
Name = "TitleText",
Position = UDim2.new(0, 1, 0, 1),
Size = UDim2.new(1, -2, 1, -2),
Text = title,
TextColor3 = TEXT_COLOR,
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size36,
BackgroundTransparency = 1
}
instance.content = Util:Create "ImageLabel" {
Parent = parent,
Name = "ContentFrame",
Position = UDim2.new(0, -1, 0, WINDOW_TITLEBAR_HEIGHT + 2),
Size = UDim2.new(1, 2, 1, -WINDOW_TITLEBAR_HEIGHT - 4),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png",
ImageColor3 = WINDOW_BG_COLOR,
ImageTransparency = WINDOW_BG_TRANSPARENCY,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(10, 10, 10, 10)
}
return setmetatable(instance, WindowFrame_mt)
end
function WindowFrame:SetTitle(title)
self.titleText.Text = title
end
function WindowFrame:AddCloseButton(callback)
self.closeButton = Util:Create "ImageButton" {
Parent = self.titlebar,
Name = "CloseButton",
Position = UDim2.new(0, CLOSE_BUTTON_OFFSET, 0, CLOSE_BUTTON_OFFSET),
Size = UDim2.new(0, CLOSE_BUTTON_SIZE, 0, CLOSE_BUTTON_SIZE),
BackgroundTransparency = 1,
Image = CLOSE_BUTTON_IMG,
SelectionImageObject = Util:Create "ImageButton" {
Name = "CloseButtonHover",
Position = UDim2.new(0, CLOSE_BUTTON_HOVER_OFFSET / -2, 0, CLOSE_BUTTON_HOVER_OFFSET / -2),
Size = UDim2.new(1, CLOSE_BUTTON_HOVER_OFFSET, 1, CLOSE_BUTTON_HOVER_OFFSET),
BackgroundTransparency = 1,
Image = CLOSE_BUTTON_HOVER
}
}
self.closeButton.MouseButton1Click:connect(callback)
end
function WindowFrame:TweenZOffsetTo(zOffset, duration, easingFunc, callback)
if self.tweener and not self.tweener:IsFinished() then
self.tweener:Cancel()
end
self.tweener = Util:TweenProperty(self, "zOffset", self.zOffset, zOffset, duration, easingFunc, callback)
end
function WindowFrame:AnimateOut(callback)
self.isAnimating = true
self:TweenZOffsetTo(ANIMATE_OUT_DISTANCE, ANIMATE_OUT_DURATION, Util:GetEaseInOutQuad(), function()
if callback then callback() end
self.isAnimating = false
end)
end
function WindowFrame:AnimateIn(callback)
self.zOffset = ANIMATE_OUT_DISTANCE
self:OnUpdate(0)
self.isAnimating = true
self:TweenZOffsetTo(0, ANIMATE_OUT_DURATION, Util:GetEaseInOutQuad(), function()
if callback then callback() end
self.isAnimating = false
end)
end
function WindowFrame:SetPopOut(popOut)
if self.isAnimating then
return
end
if popOut then
self.isPopping = true
self:TweenZOffsetTo(POPOUT_DISTANCE, POPOUT_DURATION, Util:GetEaseInOutQuad(), function()
self.isPopping = false
end)
else
self.isPopping = true
self:TweenZOffsetTo(0, POPOUT_DURATION, Util:GetEaseInOutQuad(), function()
self.isPopping = false
end)
end
end
function WindowFrame:OnUpdate(dt)
self.panel.localCF = self.zeroCF * CFrame.new(0, 0, self.zOffset)
self.panel.needsLocalPositionUpdate = self.isAnimating or self.isPopping
if self.isPopping then
local alpha = math.max(0, math.min(1, self.zOffset / POPOUT_DISTANCE))
self.titlebar.ImageColor3 = BLURRED_TITLEBAR_COLOR:lerp(FOCUSED_TITLEBAR_COLOR, alpha)
end
end
end
--Notifications panel setup
do
notificationsPanel:SetType(Panel3D.Type.Standard)
notificationsPanel:SetVisible(false)
notificationsPanel:SetCanFade(false)
notificationsPanel:ResizeStuds(leftPanelWidth, totalHeight, PIXELS_PER_STUD)
local notificationsFrame = Util:Create "TextButton" {
Parent = notificationsPanel:GetGUI(),
Name = "NotificationsListFrame",
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, -4, 1, 0),
BackgroundTransparency = 1,
Text = "",
Selectable = true,
SelectionImageObject = emptySelectionImage
}
notificationsWindow = WindowFrame.new(notificationsPanel, notificationsFrame, "Notifications")
notificationsWindow:AddCloseButton(function()
NotificationHubModule:SetVisible(false)
end)
function notificationsPanel:OnUpdate(dt)
notificationsWindow:OnUpdate(dt)
end
end
--Details panel setup
do
detailsPanel:SetType(Panel3D.Type.Standard)
detailsPanel:SetVisible(false)
detailsPanel:SetCanFade(false)
detailsPanel:ResizeStuds(rightPanelWidth, totalHeight, PIXELS_PER_STUD)
local detailsFrame = Util:Create "TextButton" {
Parent = detailsPanel:GetGUI(),
Name = "NotificationsDetailFrame",
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Text = "",
Selectable = true,
SelectionImageObject = emptySelectionImage
}
detailsWindow = WindowFrame.new(detailsPanel, detailsFrame, "Friend Requests")
function detailsPanel:OnUpdate(dt)
detailsWindow:OnUpdate(dt)
end
end
local notificationsGroups = {}
local notificationsGroupsList = {}
local function groupSort(a, b)
return a.order < b.order
end
local activeGroup = nil
local function layoutNotificationsGroups()
local y = NOTIFICATION_PADDING_Y
for _, group in ipairs(notificationsGroupsList) do
if #group.notifications > 0 then
local height = NOTIFICATION_HEIGHT_OFFSET + (NOTIFICATION_PADDING_Y * (math.min(MAX_NOTIFICATIONS_SHOWN, #group.notifications) - 1))
local widthOffset = -((MAX_NOTIFICATIONS_SHOWN - 1) * NOTIFICATION_PADDING_Y)
group.frame.Position = UDim2.new(NOTIFICATION_PADDING_X_SCALE, 0, 0, y)
group.frame.Size = UDim2.new(NOTIFICATION_WIDTH_SCALE, widthOffset, 0, height)
y = y + height + NOTIFICATION_PADDING_Y
if group.notificationsDirty then
group.notificationsDirty = false
local notificationOffset = 0
local notificationDepth = 0
local notificationsEnd = #group.notifications
if notificationsEnd > 0 then
local notificationsStart = math.max(1, notificationsEnd - MAX_NOTIFICATIONS_SHOWN + 1)
for i = 1, notificationsEnd do
local notification = group.notifications[i]
if i >= notificationsStart then
notification.frame.Visible = true
notification.frame.Position = UDim2.new(0, notificationOffset, 0, notificationOffset)
notificationOffset = notificationOffset + NOTIFICATION_PADDING_Y
local subpanel = notificationsPanel:SetSubpanelDepth(notification.frame, notificationDepth)
notificationDepth = notificationDepth + NOTIFICATION_DEPTH_OFFSET
else
notification.frame.Visible = false
end
end
if activeGroup == group then
notificationsStart = math.max(1, notificationsEnd - MAX_DETAILS_SHOWN + 1)
local detailY = 0
local fraction = 1 / MAX_DETAILS_SHOWN
for i = 1, notificationsEnd do
local notification = group.notifications[i]
if i >= notificationsStart then
notification.detailsFrame.Visible = true
notification.detailsFrame.Position = UDim2.new(0, DETAILS_PADDING, detailY, DETAILS_PADDING)
notification.detailsFrame.Size = UDim2.new(1, -DETAILS_PADDING * 2, fraction, -DETAILS_PADDING * 2)
detailY = detailY + fraction
else
notification.detailsFrame.Visible = false
end
end
end
end
end
end
end
end
local NotificationGroup = {}
do
local NotificationGroup_mt = { __index = NotificationGroup }
function NotificationGroup.new(key, title, order)
local self = setmetatable({}, NotificationGroup_mt)
self.key = key
self.title = title
self.order = order
self.notifications = {}
self.notificationsDirty = false
self.frame = Util:Create "Frame" {
Parent = notificationsWindow.content,
Name = "NotificationGroup",
BackgroundTransparency = 1
}
self.detailsFrame = Util:Create "Frame" {
Parent = nil,
BackgroundTransparency = 1,
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, 0)
}
notificationsGroups[key] = self
table.insert(notificationsGroupsList, self)
return self
end
function NotificationGroup:Deactivate()
self.detailsFrame.Parent = nil
for i, v in pairs(self.detailsFrame:GetChildren()) do
detailsPanel:RemoveSubpanel(v)
end
end
function NotificationGroup:SwitchTo()
detailsWindow:SetTitle(self.title)
for i, v in pairs(notificationsGroups) do
if v ~= self then
v:Deactivate()
end
end
self.detailsFrame.Parent = detailsWindow.content
activeGroup = self
self.notificationsDirty = true
end
function NotificationGroup:BringNotificationToFront(notification)
if activeGroup ~= self then
self:SwitchTo()
end
if #self.notifications ~= 0 and notification == self.notifications[#self.notifications] then
layoutNotificationsGroups()
return --already on top, no point
end
for i, v in ipairs(self.notifications) do
if v == notification then
--take it out
table.remove(self.notifications, i)
break
end
end
--put it back on top
table.insert(self.notifications, notification)
self.notificationsDirty = true
layoutNotificationsGroups()
end
function NotificationGroup:RemoveNotification(notification)
for i, v in ipairs(self.notifications) do
if v == notification then
table.remove(self.notifications, i)
notificationsPanel:RemoveSubpanel(notification.frame)
detailsPanel:RemoveSubpanel(notification.detailsFrame)
notification.detailsFrame:Destroy()
notification.frame:Destroy()
self.notificationsDirty = true
layoutNotificationsGroups()
return
end
end
end
function NotificationGroup:GetTopNotification()
local numNotifications = #self.notifications
if numNotifications <= 0 then
return nil
end
return self.notifications[numNotifications]
end
end
NotificationGroup.new("Friends", "Friends", 1)
NotificationGroup.new("BadgeAwards", "Badges", 2)
NotificationGroup.new("PlayerPoints", "Points", 3)
if IsDeveloperGroupEnabled() then
NotificationGroup.new("Developer", "Other", 4)
end
table.sort(notificationsGroupsList, groupSort)
local function doCallback(callback, ...)
if not callback then
return
end
if type(callback) == "function" then
callback(...)
return
end
if callback:IsA("BindableEvent") then
callback:Fire(...)
return
end
if callback:IsA("BindableFunction") then
callback:Invoke(...)
return
end
end
local Notification = {}
do
local Notification_mt = { __index = Notification }
function Notification.new(group, notificationInfo)
local self = setmetatable({}, Notification_mt)
self.group = group
self.frame = Util:Create "ImageButton" {
Parent = group.frame,
Size = UDim2.new(1, 0, 0, NOTIFICATION_HEIGHT_OFFSET),
SelectionImageObject = emptySelectionImage,
BackgroundTransparency = 1 --when we have proper frame rendering with AA, we can change this and remove the stand-in background
}
self.frame.MouseButton1Click:connect(function()
self:OnClicked()
end)
self.background = Util:Create "ImageLabel" { --this is the stand-in background for that smoooooooth edge rendering
Parent = self.frame,
Position = UDim2.new(0, -1, 0, -1),
Size = UDim2.new(1, 2, 1, 2),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/vr/rectBackgroundWhite.png",
ImageColor3 = NOTIFICATION_BG_COLOR,
ImageTransparency = NOTIFICATION_BG_TRANSPARENCY,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(10, 10, 10, 10)
}
self.imageBackground = Util:Create "ImageLabel" {
Parent = self.frame,
Position = UDim2.new(0, 5, 0, 5),
Size = UDim2.new(0, 70, 0, 70),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/circleWhite.png",
ImageColor3 = notificationInfo.imgBackgroundColor or Color3.new(1, 1, 1)
}
self.image = Util:Create "ImageLabel" {
Parent = self.imageBackground,
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Image = notificationInfo.Image
}
local text = notificationInfo.Text
if notificationInfo.Title and notificationInfo.Text then
text = ("%s\n%s"):format(notificationInfo.Title, notificationInfo.Text)
end
self.text = Util:Create "TextLabel" {
Parent = self.frame,
Position = UDim2.new(0, NOTIFICATION_HEIGHT_OFFSET, 0, 0),
Size = UDim2.new(1, -NOTIFICATION_HEIGHT_OFFSET, 1, 0),
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
Text = text,
TextWrapped = true,
Font = Enum.Font.SourceSans,
FontSize = Enum.FontSize.Size18,
TextColor3 = TEXT_COLOR
}
self.detailsFrame = Util:Create "Frame" {
Parent = group.detailsFrame,
BackgroundTransparency = 1
}
self.detailsFrame.MouseEnter:connect(function()
detailsPanel:SetSubpanelDepth(self.detailsFrame, 0.25)
end)
self.detailsFrame.MouseLeave:connect(function()
detailsPanel:SetSubpanelDepth(self.detailsFrame, 0)
end)
self.detailsBackground = Util:Create "ImageLabel" {
Parent = self.detailsFrame,
Position = UDim2.new(0, -1, 0, -1),
Size = UDim2.new(1, 2, 1, 2),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/rectBackgroundWhite.png",
ImageColor3 = Color3.new(0.2, 0.2, 0.2),
ImageTransparency = 0.1,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(10,10,10,10)
}
self.detailsIconBackground = Util:Create "ImageLabel" {
Parent = self.detailsFrame,
Position = UDim2.new(0, 20, 0, 10),
Size = UDim2.new(0, 80, 0, 80),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/circleWhite.png",
ImageColor3 = notificationInfo.imgBackgroundColor or Color3.new(1, 1, 1)
}
self.detailsIcon = Util:Create "ImageLabel" {
Parent = self.detailsIconBackground,
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Image = notificationInfo.Image
}
local detailText = notificationInfo.DetailText or notificationInfo.Title
self.detailsText = Util:Create "TextLabel" {
Parent = self.detailsFrame,
Position = UDim2.new(0, 110, 0, 10),
Size = UDim2.new(1, -120, 0, 90),
BackgroundTransparency = 1,
Text = detailText,
TextWrapped = true,
TextColor3 = TEXT_COLOR,
TextXAlignment = Enum.TextXAlignment.Left,
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size36
}
local function createButton(xPosScale, xSizeScale, text)
local button, text = Util:Create "ImageButton" {
Parent = self.detailsFrame,
Position = UDim2.new(xPosScale, 0, BUTTON_Y_POS, 0),
Size = UDim2.new(xSizeScale, 0, BUTTON_Y_SIZE, 0),
BackgroundTransparency = 1,
Image = BUTTON_NORMAL_IMG,
ScaleType = Enum.ScaleType.Slice,
SliceCenter = Rect.new(8,6,46,44),
SelectionImageObject = emptySelectionImage
}, Util:Create "TextLabel" {
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, -6),
BackgroundTransparency = 1,
Text = text,
TextColor3 = TEXT_COLOR,
Font = Enum.Font.SourceSansBold,
FontSize = Enum.FontSize.Size24
}
text.Parent = button
button.SelectionGained:connect(function()
button.Image = BUTTON_SELECTED_IMG
end)
button.SelectionLost:connect(function()
button.Image = BUTTON_NORMAL_IMG
end)
return button, text
end
if notificationInfo.Button1Text and notificationInfo.Button2Text then
self.detailsButton1, self.detailsButton1Text = createButton(BUTTON_1_POS, BUTTON_DOUBLE_SIZE, notificationInfo.Button1Text)
self.detailsButton2, self.detailsButton2Text = createButton(BUTTON_2_POS, BUTTON_DOUBLE_SIZE, notificationInfo.Button2Text)
self.detailsButton1.MouseButton1Click:connect(function()
doCallback(notificationInfo.Callback, notificationInfo.Button1Text)
self:Dismiss()
end)
self.detailsButton2.MouseButton1Click:connect(function()
doCallback(notificationInfo.Callback, notificationInfo.Button2Text)
self:Dismiss()
end)
elseif not notificationInfo.button2Text then
local text = notificationInfo.Button1Text or "Dismiss"
self.detailsButton1, self.detailsButton1Text = createButton(BUTTON_1_POS, BUTTON_SINGLE_SIZE, text)
self.detailsButton1.MouseButton1Click:connect(function()
doCallback(notificationInfo.Callback, notificationInfo.Button1Text)
self:Dismiss()
end)
end
table.insert(group.notifications, self)
group.notificationsDirty = true
layoutNotificationsGroups()
return self
end
function Notification:OnClicked()
--We don't want this functionality anymore, but I'd like to keep this commented
--out for now since this design is still in a state of flux
--self.group:BringNotificationToFront(self)
self.group:SwitchTo()
layoutNotificationsGroups()
end
function Notification:Dismiss()
self.group:RemoveNotification(self)
end
end
--NotificationHubModule API and state management
do
local pendingNotifications = {}
local isVisible = false
local unreadCount = 0
local SendNotificationInfoEvent = RobloxGui:WaitForChild("SendNotificationInfo")
SendNotificationInfoEvent.Event:connect(function(notificationInfo)
local group = notificationInfo.GroupName and notificationsGroups[notificationInfo.GroupName] --avoid error by nil index
if not group then
if IsDeveloperGroupEnabled() then
group = notificationsGroups.Developer
else
return --ignore it, invalid group
end
end
Notification.new(group, notificationInfo)
if not isVisible then
unreadCount = unreadCount + 1
NotificationHubModule.UnreadCountChanged(unreadCount)
end
end)
local menuCloseShortcutBindName = "NotificationsMenuCloseShortcut"
local function onMenuCloseShortcut(actionName, inputState, inputObj)
if inputState == Enum.UserInputState.Begin then
NotificationHubModule:SetVisible(false)
end
end
NotificationHubModule.VisibilityStateChanged = Util:Create "BindableEvent" {
Name = "VisibilityStateChanged"
}
function NotificationHubModule:GetNumberOfPendingNotifications()
return #pendingNotifications
end
function NotificationHubModule:IsVisible()
return isVisible
end
function NotificationHubModule:SetVisible(visible)
if isVisible == visible then
return
end
isVisible = visible
local topbarPanel = Panel3D.Get("Topbar3D")
topbarPanel:SetCanFade(not visible)
if visible then
unreadCount = 0
NotificationHubModule.UnreadCountChanged(unreadCount)
notificationsPanel.localCF = CFrame.new(leftOffset, 0, 0)
notificationsWindow.zeroCF = notificationsPanel.localCF
if not NO_TRANSITION_ANIMATIONS then
notificationsWindow:AnimateIn(nil)
end
detailsPanel.localCF = CFrame.new(rightOffset, 0, 0)
detailsWindow.zeroCF = detailsPanel.localCF
if not NO_TRANSITION_ANIMATIONS then
detailsWindow:AnimateIn(nil)
end
notificationsPanel:SetVisible(true)
detailsPanel:SetVisible(true)
ContextActionService:BindCoreAction(menuCloseShortcutBindName, onMenuCloseShortcut, false, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonStart)
VRHub:FireModuleOpened(NotificationHubModule.ModuleName)
else
if not NO_TRANSITION_ANIMATIONS then
spawn(function()
notificationsWindow:AnimateOut(function()
notificationsPanel:SetVisible(false)
end)
detailsWindow:AnimateOut(function()
detailsPanel:SetVisible(false)
end)
end)
else
notificationsPanel:SetVisible(false)
detailsPanel:SetVisible(false)
end
ContextActionService:UnbindCoreAction(menuCloseShortcutBindName)
VRHub:FireModuleClosed(NotificationHubModule.ModuleName)
end
NotificationHubModule.VisibilityStateChanged:Fire(visible)
end
VRHub.ModuleOpened.Event:connect(function(moduleName, isExclusive, shouldCloseNonExclusive, shouldKeepTopbarOpen)
if moduleName ~= NotificationHubModule.ModuleName then
NotificationHubModule:SetVisible(false)
end
end)
end
return NotificationHubModule
@@ -0,0 +1,85 @@
-- NotifierHint3D.lua --
-- Written by Kip Turner, copyright ROBLOX 2016 --
local NotifierHint = {}
local CoreGui = game:GetService('CoreGui')
local RunService = game:GetService('RunService')
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Util = require(RobloxGui.Modules.Settings.Utility)
local NotificationObject = Util:Create'ImageLabel'
{
Name = 'NotificationObject';
-- These numbers are a bit funny to fit the screen of ROBLOX VR
Position = UDim2.new(0.5, -860, 1, -1 - 300);
Size = UDim2.new(0, 1700,0, 700 + 300);
BackgroundTransparency = 1;
Image = "rbxasset://textures/ui/VR/notifier_glow.png";
ImageTransparency = 0;
BorderSizePixel = 0;
}
NotifierHint.DEFAULT_DURATION = 5
local function CreateNotificationEffect()
local this = {}
local speed = 2.5
local MAX_OPACITY = 0.5
local WAVE_START_SHIFT = math.pi/2
local renderConn = nil
function this:Init(duration)
local start = tick()
local endTime = start + duration
if renderConn then
renderConn:disconnect()
renderConn = nil
end
renderConn = RunService.RenderStepped:connect(function()
local now = tick()
if now >= endTime then
self:Cancel()
return
end
NotificationObject.Parent = RobloxGui
local tweenPositionOnSineWave = math.sin((tick() - start) * speed + WAVE_START_SHIFT)
-- Restrict the sine wave to only positive values
tweenPositionOnSineWave = (tweenPositionOnSineWave + 1) / 2
-- Keep the transparency in the range
NotificationObject.ImageTransparency = tweenPositionOnSineWave * (1 - MAX_OPACITY) + MAX_OPACITY
end)
end
function this:Cancel()
if renderConn then
renderConn:disconnect()
renderConn = nil
end
NotificationObject.Parent = nil
end
return this
end
local NotifierEffect = CreateNotificationEffect()
function NotifierHint:BeginNotification(duration)
self:CancelNotification()
NotifierEffect:Init(duration or self.DEFAULT_DURATION)
end
function NotifierHint:CancelNotification()
NotifierEffect:Cancel()
end
return NotifierHint
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
local InputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local VRHub = require(RobloxGui.Modules.VR.VRHub)
local Util = require(RobloxGui.Modules.Settings.Utility)
local cancelShortcutName = "CancelRecenterShortcut"
local visible = false
local RecenterModule = {}
RecenterModule.ModuleName = "Recenter"
RecenterModule.KeepVRTopbarOpen = true
RecenterModule.VRIsExclusive = true
RecenterModule.VRClosesNonExclusive = false
VRHub:RegisterModule(RecenterModule)
local countdownPanel = Panel3D.Get("RecenterCountdown")
countdownPanel:SetType(Panel3D.Type.HorizontalFollow)
countdownPanel:ResizeStuds(5, 3, 128)
countdownPanel:SetCanFade(false)
local countdown = Util:Create "TextLabel" {
Parent = countdownPanel:GetGUI(),
Position = UDim2.new(0.5, -64, 0.5, -64),
Size = UDim2.new(0, 128, 0, 128),
BackgroundTransparency = 0.9,
BackgroundColor3 = Color3.new(0.2, 0.2, 0.2),
TextColor3 = Color3.new(1, 1, 1),
Text = "",
TextScaled = true,
Font = Enum.Font.SourceSansBold,
Visible = true
}
local recenterFrame = Util:Create "ImageLabel" {
Parent = countdownPanel:GetGUI(),
Position = UDim2.new(0, 0, 0, 0),
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Image = "rbxasset://textures/ui/VR/recenterFrame.png"
}
countdownPanel:SetVisible(false)
local isCountingDown = false
local function cancelCountdown()
isCountingDown = false
countdownPanel:SetVisible(false)
end
VRHub.ModuleOpened.Event:connect(function(moduleName)
if moduleName ~= RecenterModule.ModuleName then
local module = VRHub:GetModule(moduleName)
if module.VRIsExclusive then
cancelCountdown()
end
end
end)
function RecenterModule:SetVisible(value)
visible = value
if visible then
if isCountingDown then
cancelCountdown()
VRHub:FireModuleClosed(RecenterModule.ModuleName)
return
else
VRHub:FireModuleOpened(RecenterModule.ModuleName)
end
spawn(function()
isCountingDown = true
countdownPanel:SetVisible(true)
ContextActionService:BindCoreAction(cancelShortcutName, function(actionName, inputState, inputObj)
if inputState == Enum.UserInputState.Begin then
cancelCountdown()
end
end, false, Enum.KeyCode.ButtonB)
for i = 3, 1, -1 do
if isCountingDown then
countdown.Text = tostring(i)
wait(1)
end
end
if isCountingDown then
InputService:RecenterUserHeadCFrame()
end
countdownPanel:SetVisible(false)
isCountingDown = false
ContextActionService:UnbindCoreAction(cancelShortcutName)
VRHub:FireModuleClosed(RecenterModule.ModuleName)
end)
else
cancelCountdown()
VRHub:FireModuleClosed(RecenterModule.ModuleName)
end
end
function RecenterModule:IsVisible()
return visible
end
return RecenterModule
@@ -0,0 +1,77 @@
--!nocheck
local VRService = game:GetService("VRService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui.RobloxGui
local Panel3D = require(RobloxGui.Modules.VR.Panel3D)
local VRHub = require(RobloxGui.Modules.VR.VRHub)
local VRKeyboard = require(RobloxGui.Modules.VR.VirtualKeyboard)
local UserGuiModule = {}
UserGuiModule.ModuleName = "UserGui"
UserGuiModule.KeepVRTopbarOpen = false
UserGuiModule.VRIsExclusive = false
UserGuiModule.VRClosesNonExclusive = false
VRHub:RegisterModule(UserGuiModule)
local userGuiPanel = Panel3D.Get(UserGuiModule.ModuleName)
userGuiPanel:SetType(Panel3D.Type.Standard)
userGuiPanel:ResizeStuds(4, 4, 128)
userGuiPanel:SetVisible(false)
VRHub.ModuleOpened.Event:connect(function(moduleName)
if moduleName ~= UserGuiModule.ModuleName then
local module = VRHub:GetModule(moduleName)
if module.VRClosesNonExclusive and userGuiPanel:IsVisible() then
UserGuiModule:SetVisible(false)
end
end
end)
local KeyboardOpen = false
local GuiVisible = false
function UserGuiModule:SetVisible(visible)
GuiVisible = visible
userGuiPanel:SetVisible(GuiVisible)
if GuiVisible then
VRHub:FireModuleOpened(UserGuiModule.ModuleName)
else
VRHub:FireModuleClosed(UserGuiModule.ModuleName)
end
-- We need to hide the UserGui when typing on the keyboard so that the textbox doesn't sink events from the keyboard
local showGui = GuiVisible and not KeyboardOpen
CoreGui:SetUserGuiRendering(true, showGui and userGuiPanel:GetPart() or nil, Enum.NormalId.Front)
end
function UserGuiModule:IsVisible()
return GuiVisible
end
function UserGuiModule:Update()
self:SetVisible(GuiVisible)
end
local function OnVREnabledChanged()
if not VRService.VREnabled then
userGuiPanel:SetVisible(false)
CoreGui:SetUserGuiRendering(false, nil, Enum.NormalId.Front)
end
end
VRService:GetPropertyChangedSignal("VREnabled"):connect(OnVREnabledChanged)
OnVREnabledChanged()
VRKeyboard.OpenedEvent:connect(function()
KeyboardOpen = true
UserGuiModule:Update()
end)
VRKeyboard.ClosedEvent:connect(function()
KeyboardOpen = false
UserGuiModule:Update()
end)
UserGuiModule:SetVisible(true)
return UserGuiModule
@@ -0,0 +1,159 @@
local VRService = game:GetService("VRService")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local CoreGui = game:GetService("CoreGui")
local RobloxGui = CoreGui.RobloxGui
local ViveController = require(RobloxGui.Modules.VR.Controllers.ViveController)
local LocalPlayer = Players.LocalPlayer
while not LocalPlayer do
Players.Changed:wait()
LocalPlayer = Players.LocalPlayer
end
local VRControllerModel = {}
VRControllerModel.__index = VRControllerModel
function VRControllerModel.new(userCFrame)
local self = setmetatable({}, VRControllerModel)
self.userCFrame = userCFrame
self.enabled = false
self.currentModel = nil
self.currentVRDeviceName = nil
self.modelIsInWorkspace = false
self.onVRDeviceChangedConn = nil
self.onCurrentCameraChangedConn = nil
self.onUserCFrameEnabledChangedConn = nil
self.onInputBeganConn = nil
self.onInputChangedConn = nil
self.onInputEndedConn = nil
return self
end
function VRControllerModel:setModelType(vrDeviceName)
if vrDeviceName ~= self.currentVRDeviceName then
self.currentVRDeviceName = vrDeviceName
if self.currentModel then
self.currentModel:destroy()
self.currentModel = nil
end
if self.currentVRDeviceName:match("Vive") then
self.currentModel = ViveController.new(self.userCFrame)
elseif self.currentVRDeviceName:match("Oculus") then
--todo: add an Oculus touch controller model
--self.currentModel = OculusTouchController.new(self.userCFrame)
end
--If the controller is enabled, put the model into the workspace
if self.enabled then
self:setModelInWorkspace(VRService:GetUserCFrameEnabled(self.userCFrame))
end
end
end
function VRControllerModel:setModelInWorkspace(inWorkspace)
if not self.currentModel then
return
end
if inWorkspace ~= self.modelIsInWorkspace then
self.modelIsInWorkspace = inWorkspace
if self.modelIsInWorkspace then
local camera = workspace.CurrentCamera
local folder = camera:FindFirstChild("VRCoreEffectParts")
if folder then
self.currentModel.model.Parent = folder
end
else
self.currentModel.model.Parent = nil
end
end
end
function VRControllerModel:setEnabled(enabled)
if enabled ~= self.enabled then
self.enabled = enabled
if self.enabled then
--Connect events
self.onVRDeviceChangedConn = VRService:GetPropertyChangedSignal("VRDeviceName"):connect(function()
self:setModelType(VRService.VRDeviceName)
end)
self:setModelType(VRService.VRDeviceName)
self.onCurrentCameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):connect(function()
self:setModelInWorkspace(VRService:GetUserCFrameEnabled(self.userCFrame))
end)
self.onUserCFrameEnabledChangedConn = VRService.UserCFrameEnabled:connect(function(userCFrame, enabled)
if userCFrame == self.userCFrame then
self:setModelInWorkspace(enabled)
end
end)
self.onInputBeganConn = UserInputService.InputBegan:connect(function(...) self:onInputBegan(...) end)
self.onInputChangedConn = UserInputService.InputChanged:connect(function(...) self:onInputChanged(...) end)
self.onInputEndedConn = UserInputService.InputEnded:connect(function(...) self:onInputEnded(...) end)
--Put the model in the workspace
self:setModelInWorkspace(VRService:GetUserCFrameEnabled(self.userCFrame))
else
--Disconnect events
if self.onVRDeviceChangedConn then self.onVRDeviceChangedConn:disconnect() self.onVRDeviceChangedConn = nil end
if self.onCurrentCameraChangedConn then self.onCurrentCameraChangedConn:disconnect() self.onCurrentCameraChangedConn = nil end
if self.onUserCFrameEnabledChangedConn then self.onUserCFrameEnabledChangedConn:disconnect() self.onUserCFrameEnabledChangedConn = nil end
if self.onInputBeganConn then self.onInputBeganConn:disconnect() self.onInputBeganConn = nil end
if self.onInputChangedConn then self.onInputChangedConn:disconnect() self.onInputChangedConn = nil end
if self.onInputEndedConn then self.onInputEndedConn:disconnect() self.onInputEndedConn = nil end
--Remove the model from the workspace
if self.currentModel then
self:setModelInWorkspace(false)
end
end
end
end
function VRControllerModel:update(dt)
if not self.enabled then
return
end
if self.currentModel then
local camera = workspace.CurrentCamera
local cameraCFrame = camera.CFrame
local controllerCFrame = VRService:GetUserCFrame(self.userCFrame)
local cframe = cameraCFrame * controllerCFrame
self.currentModel:setCFrame(cframe)
end
end
function VRControllerModel:onInputBegan(inputObject, wasProcessed)
if not self.enabled or not self.modelIsInWorkspace then
return
end
if self.currentModel then
self.currentModel:onInputBegan(inputObject)
end
end
function VRControllerModel:onInputChanged(inputObject, wasProcessed)
if not self.enabled or not self.modelIsInWorkspace then
return
end
if self.currentModel then
self.currentModel:onInputChanged(inputObject)
end
end
function VRControllerModel:onInputEnded(inputObject, wasProcessed)
if self.currentModel then
self.currentModel:onInputEnded(inputObject)
end
end
return VRControllerModel
@@ -0,0 +1,181 @@
--Modules/VR/VRHub.lua
--Handles all global VR state that isn't built into a specific module.
--Written by 0xBAADF00D (Kyle) on 6/10/16
local StarterGui = game:GetService("StarterGui")
local VRService = game:GetService("VRService")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local CoreGui = game:GetService("CoreGui")
local UserInputService = game:GetService("UserInputService")
local RobloxGui = CoreGui.RobloxGui
local Util = require(RobloxGui.Modules.Settings.Utility)
local LaserPointer = require(RobloxGui.Modules.VR.LaserPointer)
local VRControllerModel = require(RobloxGui.Modules.VR.VRControllerModel)
local VRHub = {}
local RegisteredModules = {}
local OpenModules = {}
--VR Setup
local vrUpdateRenderstepName = HttpService:GenerateGUID(true)
VRHub.LaserPointer = nil
VRHub.ControllerModelsEnabled = false
VRHub.LeftControllerModel = nil
VRHub.RightControllerModel = nil
StarterGui:RegisterSetCore("VRLaserPointerMode", function(mode)
if not VRHub.LaserPointer then
return
end
if not mode or not tostring(mode) then
return
end
VRHub.LaserPointer:setMode(LaserPointer.Mode[tostring(mode)] or LaserPointer.Mode.Disabled)
end)
local function enableControllerModels(enabled)
if enabled ~= VRHub.ControllerModelsEnabled then
VRHub.ControllerModelsEnabled = enabled
if enabled then
if not VRHub.LeftControllerModel then
VRHub.LeftControllerModel = VRControllerModel.new(Enum.UserCFrame.LeftHand)
end
VRHub.LeftControllerModel:setEnabled(true)
if not VRHub.RightControllerModel then
VRHub.RightControllerModel = VRControllerModel.new(Enum.UserCFrame.RightHand)
end
VRHub.RightControllerModel:setEnabled(true)
else
if VRHub.LeftControllerModel then
VRHub.LeftControllerModel:setEnabled(false)
end
if VRHub.RightControllerModel then
VRHub.RightControllerModel:setEnabled(false)
end
end
end
end
local enableControllerModelsSetByDeveloper = false
StarterGui:RegisterSetCore("VREnableControllerModels", function(enabled)
enableControllerModelsSetByDeveloper = true
enableControllerModels(enabled)
end)
local start = tick()
local function onRenderSteppedLast()
local now = tick()
local dt = now - start
start = now
if VRHub.LaserPointer then
VRHub.LaserPointer:update(dt)
end
if VRHub.LeftControllerModel then
VRHub.LeftControllerModel:update(dt)
end
if VRHub.RightControllerModel then
VRHub.RightControllerModel:update(dt)
end
end
local function onVREnabled(property)
if property ~= "VREnabled" then
return
end
if VRService.VREnabled then
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
if not VRHub.LaserPointer then
VRHub.LaserPointer = LaserPointer.new()
end
--Check again in case creating the laser pointer gracefully failed
if VRHub.LaserPointer then
VRHub.LaserPointer:setMode(LaserPointer.Mode.Navigation)
end
if not enableControllerModelsSetByDeveloper then
enableControllerModels(true)
end
RunService:BindToRenderStep(vrUpdateRenderstepName, Enum.RenderPriority.Last.Value, onRenderSteppedLast)
else
if VRHub.LaserPointer then
VRHub.LaserPointer:setMode(LaserPointer.Mode.Disabled)
end
RunService:UnbindFromRenderStep(vrUpdateRenderstepName)
end
end
onVREnabled("VREnabled")
VRService.Changed:connect(onVREnabled)
--VRHub API
function VRHub:RegisterModule(module)
RegisteredModules[module.ModuleName] = module
end
function VRHub:GetModule(moduleName)
return RegisteredModules[moduleName]
end
function VRHub:IsModuleOpened(moduleName)
return OpenModules[moduleName] ~= nil
end
function VRHub:GetOpenedModules()
local result = {}
for _, openModule in pairs(OpenModules) do
table.insert(result, openModule)
end
return result
end
VRHub.ModuleOpened = Util:Create "BindableEvent" {
Name = "VRModuleOpened"
}
--Wrapper function to document the arguments to the event
function VRHub:FireModuleOpened(moduleName)
if not RegisteredModules[moduleName] then
error("Tried to open module that is not registered: " .. moduleName)
end
if OpenModules[moduleName] ~= RegisteredModules[moduleName] then
OpenModules[moduleName] = RegisteredModules[moduleName]
VRHub.ModuleOpened:Fire(moduleName)
end
end
VRHub.ModuleClosed = Util:Create "BindableEvent" {
Name = "VRModuleClosed"
}
--Wrapper function to document the arguments to the event
function VRHub:FireModuleClosed(moduleName)
if not RegisteredModules[moduleName] then
error("Tried to close module that is not registered: " .. moduleName)
end
if OpenModules[moduleName] ~= nil then
OpenModules[moduleName] = nil
VRHub.ModuleClosed:Fire(moduleName)
end
end
function VRHub:KeepVRTopbarOpen()
for moduleName, openModule in pairs(OpenModules) do
if openModule.KeepVRTopbarOpen then
return true
end
end
return false
end
return VRHub
File diff suppressed because it is too large Load Diff