add gs
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"lint": {
|
||||
"SameLineStatement": "fatal",
|
||||
"LocalShadow": "fatal",
|
||||
"LocalUnused": "fatal",
|
||||
"FunctionUnused": "fatal",
|
||||
"ImportUnused": "fatal",
|
||||
"ImplicitReturn": "fatal"
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(emotes)
|
||||
return {
|
||||
emotes = emotes,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(equippedEmotes)
|
||||
return {
|
||||
equippedEmotes = equippedEmotes,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(segmentIndex)
|
||||
return {
|
||||
segmentIndex = segmentIndex,
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function()
|
||||
return {}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function()
|
||||
return {}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(numberEmotesLoaded)
|
||||
return {
|
||||
numberEmotesLoaded = numberEmotesLoaded,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(guiInset)
|
||||
return {
|
||||
guiInset = guiInset,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(layout)
|
||||
return {
|
||||
layout = layout,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(locale)
|
||||
return {
|
||||
locale = locale,
|
||||
}
|
||||
end)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function(errorText)
|
||||
return {
|
||||
errorText = errorText,
|
||||
}
|
||||
end)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Action = require(CorePackages.AppTempCommon.Common.Action)
|
||||
|
||||
return Action(script.Name, function()
|
||||
return {}
|
||||
end)
|
||||
@@ -0,0 +1,59 @@
|
||||
local Players = game:GetService("Players")
|
||||
local Stats = game:GetService("Stats")
|
||||
|
||||
local EmotesAnalytics = {}
|
||||
EmotesAnalytics.__index = EmotesAnalytics
|
||||
|
||||
EmotesAnalytics.EventName = {
|
||||
MenuOpened = "emoteMenuOpened",
|
||||
EmotePlayed = "emoteTriggered",
|
||||
}
|
||||
|
||||
function EmotesAnalytics.new()
|
||||
local self = {
|
||||
eventStreamImpl = nil,
|
||||
}
|
||||
setmetatable(self, EmotesAnalytics)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function EmotesAnalytics:withEventStream(eventStreamImpl)
|
||||
self.eventStreamImpl = eventStreamImpl
|
||||
return self
|
||||
end
|
||||
|
||||
function EmotesAnalytics:onMenuOpened()
|
||||
local eventContext = "emoteMenu"
|
||||
local eventName = self.EventName.MenuOpened
|
||||
|
||||
local additionalArgs = {
|
||||
pid = tostring(game.PlaceId),
|
||||
uid = tostring(Players.LocalPlayer.UserId),
|
||||
btid = Stats:GetBrowserTrackerId(),
|
||||
}
|
||||
|
||||
self:_getEventStream():setRBXEventStream(eventContext, eventName, additionalArgs)
|
||||
end
|
||||
|
||||
function EmotesAnalytics:onEmotePlayed(slotNumber, assetId)
|
||||
local eventContext = "emoteMenu"
|
||||
local eventName = self.EventName.EmotePlayed
|
||||
|
||||
local additionalArgs = {
|
||||
pid = tostring(game.PlaceId),
|
||||
uid = tostring(Players.LocalPlayer.UserId),
|
||||
btid = Stats:GetBrowserTrackerId(),
|
||||
slotNumber = slotNumber,
|
||||
assetID = assetId,
|
||||
}
|
||||
|
||||
self:_getEventStream():setRBXEventStream(eventContext, eventName, additionalArgs)
|
||||
end
|
||||
|
||||
function EmotesAnalytics:_getEventStream()
|
||||
assert(self.eventStreamImpl, "EventStream implementation not found. Did you forget to construct withEventStream?")
|
||||
return self.eventStreamImpl
|
||||
end
|
||||
|
||||
return EmotesAnalytics
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
local ContentProvider = game:GetService("ContentProvider")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local GetFFlagUseThumbnailUrl = require(game:GetService("CoreGui").RobloxGui.Modules.Common.Flags.GetFFlagUseThumbnailUrl)
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
local Thunks = EmotesMenu.Thunks
|
||||
local Utility = EmotesMenu.Utility
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local GetSegmentFromPosition = require(Utility.GetSegmentFromPosition)
|
||||
|
||||
local FocusSegment = require(Actions.FocusSegment)
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
local PlayEmote = require(Thunks.PlayEmote)
|
||||
|
||||
local EmotesButtons = Roact.PureComponent:extend("EmotesButtons")
|
||||
|
||||
local function positionInsideOuterCircle(frame, centerRelativePos)
|
||||
local outerCircleRadius = frame.AbsoluteSize.X / 2
|
||||
local innerCircleRadius = outerCircleRadius * Constants.InnerCircleSizeRatio
|
||||
|
||||
return centerRelativePos.Magnitude > innerCircleRadius and centerRelativePos.Magnitude < outerCircleRadius
|
||||
end
|
||||
|
||||
local function getSegmentFromInput(frame, input)
|
||||
local position = Vector2.new(input.Position.X, input.Position.Y)
|
||||
position = position - frame.AbsolutePosition
|
||||
position = position - frame.AbsoluteSize / 2
|
||||
|
||||
-- Invert Y position
|
||||
-- If the Y value is negative then the position is above the center of the emotes wheel
|
||||
position = Vector2.new(position.X, -position.Y)
|
||||
|
||||
if not positionInsideOuterCircle(frame, position) then
|
||||
return 0
|
||||
end
|
||||
|
||||
return GetSegmentFromPosition(position)
|
||||
end
|
||||
|
||||
local function getRandomAssetId(emotesAssetIds)
|
||||
if #emotesAssetIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local rand = math.random(1, #emotesAssetIds)
|
||||
return emotesAssetIds[rand]
|
||||
end
|
||||
|
||||
local function getEmoteImage(assetId)
|
||||
if GetFFlagUseThumbnailUrl() then
|
||||
return Constants.EmotesImage:format(assetId)
|
||||
else
|
||||
return ContentProvider.BaseUrl.. Constants.EmotesImageApi:format(assetId)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesButtons:render()
|
||||
local emotesPage = self.props.emotesPage.currentEmotes
|
||||
local emotesInfoTable = self.props.emotesPage.emotesInfo
|
||||
local emoteButtons = {}
|
||||
|
||||
for segmentIndex, emoteName in pairs(emotesPage) do
|
||||
local emoteAssetIds = emotesInfoTable[emoteName]
|
||||
|
||||
if segmentIndex > Constants.EmotesPerPage then
|
||||
warn("EmotesMenu: Number of emotes in page exceeds max emotes per page")
|
||||
break
|
||||
end
|
||||
|
||||
local angle = (360 / Constants.EmotesPerPage) * (segmentIndex - 1) + Constants.SegmentsStartRotation
|
||||
local radius = Constants.InnerCircleSizeRatio / 2
|
||||
|
||||
local spaceAvailable = (1 - Constants.InnerCircleSizeRatio) / 2
|
||||
local imageSize = spaceAvailable - Constants.SlotNumberSize - Constants.ImageOutsidePadding
|
||||
|
||||
local imagePadding = imageSize / 2 + Constants.SlotNumberSize - Constants.ImageOutsidePadding / 2
|
||||
|
||||
local cos = math.cos(math.rad(angle))
|
||||
local xRadiusPos = radius * cos
|
||||
|
||||
local xPadding = imagePadding * cos
|
||||
local xPos = 0.5 + xRadiusPos + xPadding
|
||||
|
||||
local sin = math.sin(math.rad(angle))
|
||||
local yRadiusPos = radius * sin
|
||||
|
||||
local yPadding = imagePadding * sin
|
||||
local yPos = 0.5 + yRadiusPos + yPadding
|
||||
|
||||
local assetId = getRandomAssetId(emoteAssetIds)
|
||||
local emoteImage = getEmoteImage(assetId)
|
||||
|
||||
emoteButtons[segmentIndex] = Roact.createElement("ImageLabel", {
|
||||
Image = emoteImage,
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(xPos, 0, yPos, 0),
|
||||
Size = UDim2.new(imageSize, 0, imageSize, 0),
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 2, -- TODO: Remove with sibling Zindex
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = 1,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("TextButton", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = "",
|
||||
|
||||
[Roact.Event.InputChanged] = function(frame, input)
|
||||
local inputType = input.UserInputType
|
||||
|
||||
if inputType == Enum.UserInputType.MouseMovement or inputType == Enum.UserInputType.Touch then
|
||||
local segmentIndex = getSegmentFromInput(frame, input)
|
||||
|
||||
if segmentIndex == self.props.emotesWheel.focusedSegmentIndex then
|
||||
return
|
||||
end
|
||||
|
||||
if self.props.emotesPage.currentEmotes[segmentIndex] then
|
||||
self.props.focusSegment(segmentIndex)
|
||||
else
|
||||
self.props.focusSegment(0)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
[Roact.Event.Activated] = function(frame, input)
|
||||
local segmentIndex = getSegmentFromInput(frame, input)
|
||||
if segmentIndex == 0 then
|
||||
self.props.hideMenu()
|
||||
return
|
||||
end
|
||||
|
||||
local emoteName = self.props.emotesPage.currentEmotes[segmentIndex]
|
||||
if not emoteName then
|
||||
return
|
||||
end
|
||||
|
||||
local emoteAssetIds = self.props.emotesPage.emotesInfo[emoteName]
|
||||
if not emoteAssetIds then
|
||||
return
|
||||
end
|
||||
|
||||
local assetId = getRandomAssetId(emoteAssetIds)
|
||||
self.props.playEmote(emoteName, segmentIndex, assetId)
|
||||
end,
|
||||
|
||||
[Roact.Event.MouseLeave] = function()
|
||||
self.props.focusSegment(0)
|
||||
end,
|
||||
}, emoteButtons)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
displayOptions = state.displayOptions,
|
||||
emotesPage = state.emotesPage,
|
||||
emotesWheel = state.emotesWheel,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
playEmote = function(emoteName, slotNumber, assetId)
|
||||
return dispatch(PlayEmote(emoteName, slotNumber, assetId))
|
||||
end,
|
||||
|
||||
focusSegment = function(segmentIndex)
|
||||
return dispatch(FocusSegment(segmentIndex))
|
||||
end,
|
||||
|
||||
hideMenu = function()
|
||||
return dispatch(HideMenu())
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(EmotesButtons)
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
local Workspace = game:GetService("Workspace")
|
||||
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
|
||||
local FFlagDisableAutoTranslateForKeyTranslatedContent
|
||||
= require(RobloxGui.Modules.Flags.FFlagDisableAutoTranslateForKeyTranslatedContent)
|
||||
|
||||
local FFlagEmotesMenuFixSelectedObject = game:DefineFastFlag("EmotesMenuFixSelectedObject", false)
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesModules = Components.Parent
|
||||
local Actions = EmotesModules.Actions
|
||||
local Thunks = EmotesModules.Thunks
|
||||
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
local SetLayout = require(Actions.SetLayout)
|
||||
|
||||
local OpenMenu = require(Thunks.OpenMenu)
|
||||
|
||||
local EmotesWheel = require(Components.EmotesWheel)
|
||||
local ErrorMessage = require(Components.ErrorMessage)
|
||||
|
||||
local Constants = require(EmotesModules.Constants)
|
||||
|
||||
local EmotesMenu = Roact.PureComponent:extend("EmotesMenu")
|
||||
|
||||
function EmotesMenu:init()
|
||||
self.savedSelectedCoreObject = nil
|
||||
self.savedSelectedObject = nil
|
||||
end
|
||||
|
||||
function EmotesMenu:bindActions()
|
||||
local function toggleMenuFunc(actionName, inputState, inputObj)
|
||||
if GuiService.MenuIsOpen then
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self.props.hideMenu()
|
||||
else
|
||||
self.props.openMenu()
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
ContextActionService:BindAction(Constants.ToggleMenuAction, toggleMenuFunc, --[[createTouchButton = ]] false,
|
||||
Constants.EmoteMenuOpenKey)
|
||||
end
|
||||
|
||||
function EmotesMenu:unbindActions()
|
||||
ContextActionService:UnbindAction(Constants.ToggleMenuAction)
|
||||
end
|
||||
|
||||
function EmotesMenu:saveSelectedObject()
|
||||
self.savedSelectedCoreObject = GuiService.SelectedCoreObject
|
||||
GuiService.SelectedCoreObject = nil
|
||||
self.savedSelectedObject = GuiService.SelectedObject
|
||||
GuiService.SelectedObject = nil
|
||||
end
|
||||
|
||||
function EmotesMenu:resetSelectedObject()
|
||||
if GuiService.SelectedCoreObject == nil then
|
||||
GuiService.SelectedCoreObject = self.savedSelectedCoreObject
|
||||
end
|
||||
if GuiService.SelectedObject == nil then
|
||||
GuiService.SelectedObject = self.savedSelectedObject
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenu:viewPortSizeChanged()
|
||||
if self.props.layout == Constants.Layout.TenFoot then
|
||||
return
|
||||
end
|
||||
|
||||
local camera = Workspace.CurrentCamera
|
||||
if not camera then
|
||||
return
|
||||
end
|
||||
|
||||
local viewportSize = camera.ViewportSize
|
||||
if viewportSize == Vector2.new(1, 1) then
|
||||
-- Viewport is not initialized yet
|
||||
return
|
||||
end
|
||||
|
||||
local layout = Constants.Layout.Large
|
||||
|
||||
local smallScreenX = viewportSize.X < Constants.SmallScreenMaxSize.X
|
||||
local smallScreenY = viewportSize.Y < Constants.SmallScreenMaxSize.Y
|
||||
|
||||
if smallScreenX or smallScreenY then
|
||||
layout = Constants.Layout.Small
|
||||
end
|
||||
|
||||
self.props.setLayout(layout)
|
||||
end
|
||||
|
||||
function EmotesMenu:currentCameraChanged()
|
||||
if self.viewportSizeChangedConn then
|
||||
self.viewportSizeChangedConn:Disconnect()
|
||||
end
|
||||
|
||||
local camera = Workspace.CurrentCamera
|
||||
if camera then
|
||||
self.viewportSizeChangedConn = camera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
|
||||
self:viewPortSizeChanged()
|
||||
end)
|
||||
self:viewPortSizeChanged()
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenu:didMount()
|
||||
self.currentCameraChangedConn = Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
|
||||
self:currentCameraChanged()
|
||||
end)
|
||||
self:currentCameraChanged()
|
||||
|
||||
self.menuOpenedConn = GuiService.MenuOpened:Connect(function()
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self.props.hideMenu()
|
||||
end
|
||||
end)
|
||||
|
||||
self.inputOutsideMenuConn = UserInputService.InputBegan:Connect(function(input, gameProcessedEvent)
|
||||
if gameProcessedEvent then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.props.displayOptions.menuVisible then
|
||||
return
|
||||
end
|
||||
|
||||
local inputType = input.UserInputType
|
||||
if inputType == Enum.UserInputType.MouseButton1 or inputType == Enum.UserInputType.Touch then
|
||||
self.props.hideMenu()
|
||||
end
|
||||
end)
|
||||
|
||||
self:bindActions()
|
||||
end
|
||||
|
||||
function EmotesMenu:willUnmount()
|
||||
self.currentCameraChangedConn:Disconnect()
|
||||
self.viewportSizeChangedConn:Disconnect()
|
||||
self.menuOpenedConn:Disconnect()
|
||||
self.inputOutsideMenuConn:Disconnect()
|
||||
|
||||
self.currentCameraChangedConn = nil
|
||||
self.viewportSizeChangedConn = nil
|
||||
self.menuOpenedConn = nil
|
||||
self.inputOutsideMenuConn = nil
|
||||
|
||||
self:unbindActions()
|
||||
end
|
||||
|
||||
if FFlagEmotesMenuFixSelectedObject then
|
||||
function EmotesMenu:didUpdate(prevProps, prevState)
|
||||
if self.props.displayOptions.menuVisible ~= prevProps.displayOptions.menuVisible then
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self:saveSelectedObject()
|
||||
else
|
||||
self:resetSelectedObject()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenu:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
local guiInset = self.props.displayOptions.guiInset
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
BorderSizePixel = 0,
|
||||
Position = UDim2.new(0, 0, 0, -guiInset),
|
||||
Size = UDim2.new(1, 0, 1, guiInset),
|
||||
ZIndex = Constants.EmotesMenuZIndex,
|
||||
AutoLocalize = not FFlagDisableAutoTranslateForKeyTranslatedContent,
|
||||
}, {
|
||||
Main = Roact.createElement("Frame", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Size = Constants.ScreenAvailable,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = 1,
|
||||
}),
|
||||
|
||||
SizeConstraint = Roact.createElement("UISizeConstraint", {
|
||||
MinSize = LayoutConstants.EmotesWheelMinSize,
|
||||
MaxSize = LayoutConstants.EmotesWheelMaxSize,
|
||||
}),
|
||||
|
||||
EmotesWheel = Roact.createElement(EmotesWheel),
|
||||
}),
|
||||
|
||||
ErrorMessage = Roact.createElement(ErrorMessage),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
displayOptions = state.displayOptions,
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
openMenu = function()
|
||||
return dispatch(OpenMenu())
|
||||
end,
|
||||
|
||||
hideMenu = function()
|
||||
return dispatch(HideMenu())
|
||||
end,
|
||||
|
||||
setLayout = function(layout)
|
||||
return dispatch(SetLayout(layout))
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(EmotesMenu)
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
local ContextActionService = game:GetService("ContextActionService")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local UserInputService = game:GetService("UserInputService")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local MouseIconOverrideService = require(CorePackages.InGameServices.MouseIconOverrideService)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
local Thunks = EmotesMenu.Thunks
|
||||
local Utility = EmotesMenu.Utility
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local GetSegmentFromPosition = require(Utility.GetSegmentFromPosition)
|
||||
|
||||
local EmotesButtons = require(Components.EmotesButtons)
|
||||
local SlotNumbers = require(Components.SlotNumbers)
|
||||
local WheelBackground = require(Components.WheelBackground)
|
||||
|
||||
local FocusSegment = require(Actions.FocusSegment)
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
local PlayEmote = require(Thunks.PlayEmote)
|
||||
|
||||
local KEYBINDS_PRIORITY = Enum.ContextActionPriority.High.Value
|
||||
|
||||
local EmotesWheel = Roact.PureComponent:extend("EmotesWheel")
|
||||
|
||||
local function getRandomAssetId(emotesAssetIds)
|
||||
if #emotesAssetIds == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local rand = math.random(1, #emotesAssetIds)
|
||||
return emotesAssetIds[rand]
|
||||
end
|
||||
|
||||
function EmotesWheel:bindActions()
|
||||
if self.actionsBound then
|
||||
return
|
||||
end
|
||||
|
||||
local function selectEmote(actionName, inputState, inputObj)
|
||||
local inputVector = Vector2.new(inputObj.Position.X, inputObj.Position.Y)
|
||||
|
||||
if inputVector.Magnitude < Constants.ThumbstickThreshold then
|
||||
return
|
||||
end
|
||||
|
||||
local segmentIndex = GetSegmentFromPosition(inputVector)
|
||||
if segmentIndex == self.props.emotesWheel.focusedSegmentIndex then
|
||||
return
|
||||
end
|
||||
|
||||
if self.props.emotesPage.currentEmotes[segmentIndex] then
|
||||
self.props.focusSegment(segmentIndex)
|
||||
else
|
||||
self.props.focusSegment(0)
|
||||
end
|
||||
end
|
||||
|
||||
ContextActionService:BindActionAtPriority(Constants.EmoteSelectionAction, selectEmote,
|
||||
--[[createTouchButton = ]] false, KEYBINDS_PRIORITY, Constants.SelectionThumbstick)
|
||||
|
||||
|
||||
local function playSelected(actionName, inputState, inputObj)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
local focusedSegment = self.props.emotesWheel.focusedSegmentIndex
|
||||
local emoteName = self.props.emotesPage.currentEmotes[focusedSegment]
|
||||
|
||||
if not emoteName then
|
||||
return
|
||||
end
|
||||
|
||||
local emoteAssetIds = self.props.emotesPage.emotesInfo[emoteName]
|
||||
if not emoteAssetIds then
|
||||
return
|
||||
end
|
||||
|
||||
local assetId = getRandomAssetId(emoteAssetIds)
|
||||
self.props.playEmote(emoteName, focusedSegment, assetId)
|
||||
end
|
||||
end
|
||||
|
||||
ContextActionService:BindActionAtPriority(Constants.PlaySelectedAction, playSelected,
|
||||
--[[createTouchButton = ]] false, KEYBINDS_PRIORITY, Constants.PlayEmoteButton)
|
||||
|
||||
|
||||
local function closeMenu(actionName, inputState, inputObj)
|
||||
if inputState == Enum.UserInputState.Begin then
|
||||
self.props.hideMenu()
|
||||
end
|
||||
end
|
||||
|
||||
local closeButtons = {
|
||||
Constants.EmoteMenuCloseKey,
|
||||
Constants.EmoteMenuCloseButton,
|
||||
Constants.EmoteMenuCloseButtonSecondary
|
||||
}
|
||||
|
||||
ContextActionService:BindActionAtPriority(Constants.CloseMenuAction, closeMenu, --[[createTouchButton = ]] false,
|
||||
KEYBINDS_PRIORITY, unpack(closeButtons))
|
||||
|
||||
|
||||
local function closeMenuNoSink(actionName, inputState, inputObj)
|
||||
closeMenu(actionName, inputState, inputObj)
|
||||
|
||||
return Enum.ContextActionResult.Pass
|
||||
end
|
||||
|
||||
ContextActionService:BindActionAtPriority(Constants.LeaveMenuDontSinkInputAction, closeMenuNoSink,
|
||||
--[[createTouchButton = ]] false, KEYBINDS_PRIORITY, unpack(Constants.LeaveMenuNoSinkInputs))
|
||||
|
||||
|
||||
local function activateEmoteByNumber(actionName, inputState, inputObj)
|
||||
if inputState ~= Enum.UserInputState.Begin then
|
||||
return
|
||||
end
|
||||
|
||||
local pressedSlot
|
||||
|
||||
for slot, key in ipairs(Constants.EmoteSlotKeys) do
|
||||
if key == inputObj.KeyCode then
|
||||
pressedSlot = slot
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not pressedSlot then
|
||||
return
|
||||
end
|
||||
|
||||
local emoteName = self.props.emotesPage.currentEmotes[pressedSlot]
|
||||
if not emoteName then
|
||||
return
|
||||
end
|
||||
|
||||
local emoteAssetIds = self.props.emotesPage.emotesInfo[emoteName]
|
||||
if not emoteAssetIds then
|
||||
return
|
||||
end
|
||||
|
||||
local assetId = getRandomAssetId(emoteAssetIds)
|
||||
self.props.playEmote(emoteName, pressedSlot, assetId)
|
||||
end
|
||||
|
||||
ContextActionService:BindActionAtPriority(Constants.ActivateEmoteSlotAction, activateEmoteByNumber,
|
||||
--[[ createTouchButton = ]] false, KEYBINDS_PRIORITY, unpack(Constants.EmoteSlotKeys))
|
||||
|
||||
self.actionsBound = true
|
||||
end
|
||||
|
||||
function EmotesWheel:unbindActions()
|
||||
if self.actionsBound then
|
||||
ContextActionService:UnbindAction(Constants.CloseMenuAction)
|
||||
ContextActionService:UnbindAction(Constants.EmoteSelectionAction)
|
||||
ContextActionService:UnbindAction(Constants.PlaySelectedAction)
|
||||
ContextActionService:UnbindAction(Constants.LeaveMenuDontSinkInputAction)
|
||||
ContextActionService:UnbindAction(Constants.ActivateEmoteSlotAction)
|
||||
|
||||
self.actionsBound = false
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesWheel:addCursorOverride()
|
||||
if self.isUsingGamepad and not self.isCursorHidden then
|
||||
MouseIconOverrideService.push(Constants.CursorOverrideName, Enum.OverrideMouseIconBehavior.ForceHide)
|
||||
|
||||
self.isCursorHidden = true
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesWheel:removeCursorOverride()
|
||||
if self.isCursorHidden then
|
||||
MouseIconOverrideService.pop(Constants.CursorOverrideName)
|
||||
|
||||
self.isCursorHidden = false
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesWheel:connectListeners()
|
||||
self.inputTypeChangedListener = UserInputService.LastInputTypeChanged:Connect(function(lastInputType)
|
||||
if Constants.GamepadInputTypes[lastInputType] then
|
||||
self.isUsingGamepad = true
|
||||
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self:addCursorOverride()
|
||||
end
|
||||
else
|
||||
self.isUsingGamepad = false
|
||||
|
||||
self:removeCursorOverride()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function EmotesWheel:disconnectListeners()
|
||||
self.inputTypeChangedListener:Disconnect()
|
||||
end
|
||||
|
||||
function EmotesWheel:didUpdate()
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self:bindActions()
|
||||
|
||||
self:addCursorOverride()
|
||||
else
|
||||
self:unbindActions()
|
||||
|
||||
self:removeCursorOverride()
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesWheel:didMount()
|
||||
if self.props.displayOptions.menuVisible then
|
||||
self:bindActions()
|
||||
end
|
||||
|
||||
self:connectListeners()
|
||||
end
|
||||
|
||||
function EmotesWheel:willUnmount()
|
||||
self:unbindActions()
|
||||
|
||||
self:disconnectListeners()
|
||||
end
|
||||
|
||||
function EmotesWheel:render()
|
||||
return Roact.createElement("Frame", {
|
||||
Active = true,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Visible = self.props.displayOptions.menuVisible,
|
||||
}, {
|
||||
MouseUnlock = Roact.createElement("TextButton", {
|
||||
Modal = true,
|
||||
Size = UDim2.new(0, 0, 0, 0),
|
||||
Text = "",
|
||||
Transparency = 1,
|
||||
}),
|
||||
|
||||
Back = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 1,
|
||||
}, {
|
||||
Background = Roact.createElement(WheelBackground),
|
||||
}),
|
||||
|
||||
Front = Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
ZIndex = 2,
|
||||
}, {
|
||||
EmotesButtons = Roact.createElement(EmotesButtons),
|
||||
SlotNumbers = Roact.createElement(SlotNumbers),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
displayOptions = state.displayOptions,
|
||||
emotesWheel = state.emotesWheel,
|
||||
emotesPage = state.emotesPage,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
playEmote = function(emoteName, slotNumber, assetId)
|
||||
return dispatch(PlayEmote(emoteName, slotNumber, assetId))
|
||||
end,
|
||||
|
||||
focusSegment = function(segmentIndex)
|
||||
return dispatch(FocusSegment(segmentIndex))
|
||||
end,
|
||||
|
||||
hideMenu = function()
|
||||
return dispatch(HideMenu())
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(EmotesWheel)
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
local HideError = require(Actions.HideError)
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local ErrorMessage = Roact.PureComponent:extend("ErrorMessage")
|
||||
|
||||
function ErrorMessage:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = LayoutConstants.ErrorFrameSize,
|
||||
Position = LayoutConstants.ErrorFramePosition,
|
||||
BackgroundColor3 = Constants.Colors.Black,
|
||||
BackgroundTransparency = Constants.ErrorFrameBackgroundTransparency,
|
||||
BorderSizePixel = Constants.ErrorFrameBorderSize,
|
||||
Selectable = false,
|
||||
Visible = self.props.displayOptions.errorVisible,
|
||||
|
||||
[Roact.Event.TouchTap] = function()
|
||||
self.props.close()
|
||||
end,
|
||||
|
||||
[Roact.Event.InputBegan] = function(_, input)
|
||||
if input.UserInputType == Enum.UserInputType.MouseButton1 then
|
||||
self.props.close()
|
||||
end
|
||||
end,
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = LayoutConstants.ErrorFrameAspectRatio,
|
||||
}),
|
||||
|
||||
SizeConstraint = Roact.createElement("UISizeConstraint", {
|
||||
MinSize = LayoutConstants.ErrorFrameMinSize,
|
||||
MaxSize = LayoutConstants.ErrorFrameMaxSize,
|
||||
}),
|
||||
|
||||
ErrorIcon = Roact.createElement("ImageLabel", {
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0, LayoutConstants.ErrorIconOffset, 0.5, 0),
|
||||
Size = UDim2.new(0.2, -LayoutConstants.ErrorIconOffset * 2, 1, -LayoutConstants.ErrorIconOffset * 2),
|
||||
Image = LayoutConstants.ErrorIcon,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = 1,
|
||||
}),
|
||||
}),
|
||||
|
||||
ErrorText = Roact.createElement("TextLabel", {
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0.2, -LayoutConstants.ErrorIconOffset, 0.5, 0),
|
||||
Size = UDim2.new(0.8, 0, 1, 0),
|
||||
Text = self.props.displayOptions.errorText,
|
||||
TextScaled = true,
|
||||
TextSize = LayoutConstants.ErrorTextSize,
|
||||
TextColor3 = Constants.Colors.White,
|
||||
TextXAlignment = Enum.TextXAlignment.Left,
|
||||
Font = LayoutConstants.ErrorTextFont,
|
||||
BackgroundTransparency = 1,
|
||||
}, {
|
||||
SizeConstraint = Roact.createElement("UISizeConstraint", {
|
||||
MaxSize = Vector2.new(LayoutConstants.ErrorFrameMaxSize.X,
|
||||
LayoutConstants.ErrorTextSize + Constants.TextPadding),
|
||||
}),
|
||||
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = LayoutConstants.ErrorTextSize,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
displayOptions = state.displayOptions,
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
local function mapDispatchToProps(dispatch)
|
||||
return {
|
||||
close = function()
|
||||
return dispatch(HideError())
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, mapDispatchToProps)(ErrorMessage)
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local SelectionEffect = Roact.PureComponent:extend("SelectionEffect")
|
||||
|
||||
function SelectionEffect:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
local focusedIndex = self.props.emotesWheel.focusedSegmentIndex
|
||||
|
||||
local angle = (360 / Constants.EmotesPerPage) * (focusedIndex - 1) + Constants.SegmentsStartRotation
|
||||
local radius = Constants.InnerCircleSizeRatio / 2
|
||||
|
||||
local segmentAngle = 360 / Constants.EmotesPerPage
|
||||
local chordLength = Constants.InnerCircleSizeRatio * math.sin(math.rad(segmentAngle / 2))
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Rotation = angle,
|
||||
Visible = focusedIndex ~= 0,
|
||||
}, {
|
||||
SelectedLine = Roact.createElement("ImageLabel", {
|
||||
AnchorPoint = Vector2.new(1, 0.5),
|
||||
Position = UDim2.new(0.5 + radius, -LayoutConstants.SelectedLinePadding, 0.5, 0),
|
||||
Size = UDim2.new(1, 0, chordLength, -LayoutConstants.SelectedLinePadding),
|
||||
BackgroundTransparency = 1,
|
||||
Image = LayoutConstants.SelectedLine,
|
||||
ZIndex = 4, -- TODO: Remove when CoreGui uses Sibling ZIndex
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = LayoutConstants.SelectedLineImageSize.X/LayoutConstants.SelectedLineImageSize.Y,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
emotesWheel = state.emotesWheel,
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(SelectionEffect)
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local SelectionGradient = Roact.PureComponent:extend("SelectionGradient")
|
||||
|
||||
function SelectionGradient:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
|
||||
local focusedIndex = self.props.emotesWheel.focusedSegmentIndex
|
||||
local angle = (360 / Constants.EmotesPerPage) * (focusedIndex - 1) + Constants.SegmentsStartRotation
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Rotation = angle,
|
||||
Visible = focusedIndex ~= 0,
|
||||
}, {
|
||||
SelectedGradient = Roact.createElement("ImageLabel", {
|
||||
AnchorPoint = Vector2.new(0, 0.5),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
Size = UDim2.new(0.5, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
ImageTransparency = Constants.GradientTransparency,
|
||||
Image = LayoutConstants.SelectedGradient,
|
||||
ZIndex = 2, -- TODO: Remove when RobloxGui uses sibling ZIndex behavior
|
||||
}, {
|
||||
AsspectRatioConstraint = Roact.createElement("UIAspectRatioConstraint", {
|
||||
AspectRatio = LayoutConstants.SelectedGradientImageSize.X / LayoutConstants.SelectedGradientImageSize.Y,
|
||||
}),
|
||||
|
||||
SizeConstraint = Roact.createElement("UISizeConstraint", {
|
||||
MaxSize = LayoutConstants.SelectedGradientImageSize,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
emotesWheel = state.emotesWheel,
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(SelectionGradient)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local SlotNumbers = Roact.PureComponent:extend("SlotNumbers")
|
||||
|
||||
function SlotNumbers:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
|
||||
local slotNumbers = {}
|
||||
|
||||
for slotIndex = 1, Constants.EmotesPerPage do
|
||||
local angle = (360 / Constants.EmotesPerPage) * (slotIndex - 1) + Constants.SegmentsStartRotation
|
||||
local radius = Constants.InnerCircleSizeRatio / 2
|
||||
|
||||
local numberSize = Constants.SlotNumberSize
|
||||
local numberPadding = numberSize / 2
|
||||
|
||||
local cos = math.cos(math.rad(angle))
|
||||
local xRadiusPos = radius * cos
|
||||
|
||||
local xPadding = numberPadding * cos
|
||||
local xPos = 0.5 + xRadiusPos + xPadding
|
||||
|
||||
local sin = math.sin(math.rad(angle))
|
||||
local yRadiusPos = radius * sin
|
||||
|
||||
local yPadding = numberPadding * sin
|
||||
local yPos = 0.5 + yRadiusPos + yPadding
|
||||
|
||||
slotNumbers[slotIndex] = Roact.createElement("TextLabel", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.new(xPos, 0, yPos, 0),
|
||||
Size = UDim2.new(numberSize, 0, numberSize, 0),
|
||||
BackgroundTransparency = 1,
|
||||
TextScaled = true,
|
||||
TextSize = LayoutConstants.SlotNumberTextSize,
|
||||
TextColor3 = Constants.Colors.White,
|
||||
Text = slotIndex,
|
||||
Font = LayoutConstants.SlotNumberFont,
|
||||
ZIndex = 2, -- TODO: Remove with Sibling ZIndex
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = LayoutConstants.SlotNumberTextSize,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
return Roact.createElement("Frame", {
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
BackgroundTransparency = 1,
|
||||
}, slotNumbers)
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(SlotNumbers)
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
local SelectionGradient = require(Components.SelectionGradient)
|
||||
local SelectionEffect = require(Components.SelectionEffect)
|
||||
local WheelText = require(Components.WheelText)
|
||||
|
||||
local WheelBackground = Roact.PureComponent:extend("WheelBackground")
|
||||
|
||||
function WheelBackground:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
|
||||
return Roact.createElement("Folder", {}, {
|
||||
BackgroundCircleOverlay = Roact.createElement("ImageLabel", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Image = LayoutConstants.CircleBackground,
|
||||
ZIndex = 1,
|
||||
}),
|
||||
|
||||
BackgroundGradient = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ZIndex = 2,
|
||||
}, {
|
||||
SelectionGradient = Roact.createElement(SelectionGradient)
|
||||
}),
|
||||
|
||||
BackgroundImage = Roact.createElement("ImageLabel", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Image = LayoutConstants.SegmentedCircle,
|
||||
ZIndex = 3,
|
||||
}),
|
||||
|
||||
Selection = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ZIndex = 4,
|
||||
}, {
|
||||
SelectionEffect = Roact.createElement(SelectionEffect),
|
||||
}),
|
||||
|
||||
BackgroundText = Roact.createElement("Frame", {
|
||||
BackgroundTransparency = 1,
|
||||
Size = UDim2.new(1, 0, 1, 0),
|
||||
ZIndex = 5,
|
||||
}, {
|
||||
MiddleText = Roact.createElement(WheelText),
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
layout = state.layout,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(WheelBackground)
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local StarterPlayer = game:GetService("StarterPlayer")
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local Components = script.Parent
|
||||
local EmotesMenu = Components.Parent
|
||||
local CoreScriptsModules = EmotesMenu.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
local RobloxTranslator = require(CoreScriptsModules.RobloxTranslator)
|
||||
|
||||
local WheelText = Roact.PureComponent:extend("WheelText")
|
||||
|
||||
function WheelText:getWheelText()
|
||||
local currentEmotes = self.props.emotesPage.currentEmotes
|
||||
local locale = self.props.locale
|
||||
local numberEmotesLoaded = self.props.emotesPage.numberEmotesLoaded
|
||||
|
||||
if next(currentEmotes) == nil then
|
||||
if StarterPlayer.UserEmotesEnabled and numberEmotesLoaded == 0 then
|
||||
return RobloxTranslator:FormatByKeyForLocale(Constants.LocalizationKeys.NoEmotesEquipped, locale)
|
||||
else
|
||||
return RobloxTranslator:FormatByKeyForLocale(Constants.LocalizationKeys.EmotesDisabled, locale)
|
||||
end
|
||||
end
|
||||
|
||||
local focusedSlot = self.props.emotesWheel.focusedSegmentIndex
|
||||
local emoteName = currentEmotes[focusedSlot]
|
||||
|
||||
if emoteName then
|
||||
return emoteName
|
||||
else
|
||||
return RobloxTranslator:FormatByKeyForLocale(Constants.LocalizationKeys.SelectAnEmote, locale)
|
||||
end
|
||||
end
|
||||
|
||||
function WheelText:render()
|
||||
local LayoutConstants = Constants.Layouts[self.props.layout]
|
||||
local wheelText = self:getWheelText()
|
||||
|
||||
return Roact.createElement("TextLabel", {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Size = UDim2.new(Constants.InnerCircleSizeRatio, -Constants.TextPadding,
|
||||
Constants.InnerCircleSizeRatio, -Constants.TextPadding),
|
||||
Position = UDim2.new(0.5, 0, 0.5, 0),
|
||||
BackgroundTransparency = 1,
|
||||
Text = wheelText,
|
||||
TextScaled = true,
|
||||
TextSize = LayoutConstants.MiddleTextSize,
|
||||
TextColor3 = Constants.Colors.White,
|
||||
Font = LayoutConstants.MiddleTextFont,
|
||||
ZIndex = 5, -- TODO: Remove when CoreGui uses Sibling ZIndex
|
||||
}, {
|
||||
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
|
||||
MaxTextSize = LayoutConstants.MiddleTextSize,
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
local function mapStateToProps(state)
|
||||
return {
|
||||
emotesWheel = state.emotesWheel,
|
||||
emotesPage = state.emotesPage,
|
||||
layout = state.layout,
|
||||
locale = state.locale,
|
||||
}
|
||||
end
|
||||
|
||||
return RoactRodux.UNSTABLE_connect2(mapStateToProps, nil)(WheelText)
|
||||
@@ -0,0 +1,168 @@
|
||||
local EmotesMenu = script.Parent
|
||||
local Layouts = EmotesMenu.Layouts
|
||||
|
||||
local SmallLayout = require(Layouts.Small)
|
||||
local LargeLayout = require(Layouts.Large)
|
||||
local TenFootLayout = require(Layouts.TenFoot)
|
||||
|
||||
local Constants = {
|
||||
EmotesPerPage = 8,
|
||||
ErrorDisplayTimeSeconds = 5,
|
||||
|
||||
FallbackLocale = "en-us",
|
||||
|
||||
LocalizationKeys = {
|
||||
SelectAnEmote = "InGame.EmotesMenu.SelectAnEmote",
|
||||
NoEmotesEquipped = "InGame.EmotesMenu.NoEmotesEquipped",
|
||||
EmotesDisabled = "InGame.EmotesMenu.EmotesDisabled",
|
||||
|
||||
ErrorMessages = {
|
||||
NotSupported = "InGame.EmotesMenu.ErrorMessageNotSupported",
|
||||
R15Only = "InGame.EmotesMenu.ErrorMessageR15Only",
|
||||
NoMatchingEmote = "InGame.EmotesMenu.ErrorMessageNoMatchingEmote",
|
||||
TemporarilyUnavailable = "InGame.EmotesMenu.ErrorMessageTemporarilyUnavailable",
|
||||
},
|
||||
},
|
||||
|
||||
EmotesImage = "rbxthumb://type=Asset&id=%d&w=420&h=420",
|
||||
--todo: remove with GetFFlagUseThumbnailUrl
|
||||
EmotesImageApi = "asset-thumbnail/image?assetId=%d&width=420&height=420&format=png",
|
||||
|
||||
EmotesMenuZIndex = 50,
|
||||
|
||||
ErrorFrameBorderSize = 0,
|
||||
ErrorFrameBackgroundTransparency = 0.3,
|
||||
|
||||
TextPadding = 10,
|
||||
|
||||
SegmentsStartRotation = -90,
|
||||
|
||||
-- Ratio of diameter of inner circle to emote wheel image width
|
||||
InnerCircleSizeRatio = 0.45,
|
||||
|
||||
-- Size of slot numbers relative to emote wheel size
|
||||
SlotNumberSize = 0.1,
|
||||
ImageOutsidePadding = 0.025,
|
||||
|
||||
GradientTransparency = 0.5,
|
||||
|
||||
CursorOverrideName = "EmotesMenuCursorOverride",
|
||||
|
||||
GamepadInputTypes = {
|
||||
[Enum.UserInputType.Gamepad1] = true,
|
||||
[Enum.UserInputType.Gamepad2] = true,
|
||||
[Enum.UserInputType.Gamepad3] = true,
|
||||
[Enum.UserInputType.Gamepad4] = true,
|
||||
[Enum.UserInputType.Gamepad5] = true,
|
||||
[Enum.UserInputType.Gamepad6] = true,
|
||||
[Enum.UserInputType.Gamepad7] = true,
|
||||
[Enum.UserInputType.Gamepad8] = true,
|
||||
},
|
||||
|
||||
-- User will leave the menu if it's open with any of these inputs but the input won't be sunk
|
||||
LeaveMenuNoSinkInputs = {
|
||||
Enum.KeyCode.W,
|
||||
Enum.KeyCode.A,
|
||||
Enum.KeyCode.S,
|
||||
Enum.KeyCode.D,
|
||||
|
||||
Enum.KeyCode.Up,
|
||||
Enum.KeyCode.Left,
|
||||
Enum.KeyCode.Down,
|
||||
Enum.KeyCode.Right,
|
||||
|
||||
Enum.KeyCode.Space,
|
||||
|
||||
Enum.KeyCode.Tab,
|
||||
Enum.KeyCode.Slash,
|
||||
Enum.KeyCode.Backquote,
|
||||
},
|
||||
|
||||
EmoteSlotKeys = {
|
||||
Enum.KeyCode.One,
|
||||
Enum.KeyCode.Two,
|
||||
Enum.KeyCode.Three,
|
||||
Enum.KeyCode.Four,
|
||||
Enum.KeyCode.Five,
|
||||
Enum.KeyCode.Six,
|
||||
Enum.KeyCode.Seven,
|
||||
Enum.KeyCode.Eight,
|
||||
},
|
||||
|
||||
EmoteMenuOpenKey = Enum.KeyCode.Period,
|
||||
|
||||
EmoteMenuCloseKey = Enum.KeyCode.Escape,
|
||||
EmoteMenuCloseButton = Enum.KeyCode.ButtonB,
|
||||
EmoteMenuCloseButtonSecondary = Enum.KeyCode.ButtonStart,
|
||||
|
||||
SelectionThumbstick = Enum.KeyCode.Thumbstick1,
|
||||
ThumbstickThreshold = 0.8,
|
||||
|
||||
PlayEmoteButton = Enum.KeyCode.ButtonA,
|
||||
|
||||
ToggleMenuAction = "EmotesMenuToggleAction",
|
||||
CloseMenuAction = "EmotesMenuCloseAction",
|
||||
EmoteSelectionAction = "EmotesMenuSelectionAction",
|
||||
ActivateEmoteSlotAction = "EmotesMenuActivateEmoteSlotAction",
|
||||
PlaySelectedAction = "EmotesMenuPlaySelectedAction",
|
||||
LeaveMenuDontSinkInputAction = "EmotesMenuLeaveMenuDontSinkInputAction",
|
||||
|
||||
-- Emotes Menu can use up to 90% of the screen horizontally and 75% vertically
|
||||
ScreenAvailable = UDim2.new(0.9, 0, 0.75, 0),
|
||||
|
||||
-- Use the Large layout if the screen is larger than this size
|
||||
-- Values taken from isSmallTouchScreen in Utility module
|
||||
SmallScreenMaxSize = Vector2.new(700, 500),
|
||||
|
||||
Layout = {
|
||||
Small = 0,
|
||||
Large = 1,
|
||||
TenFoot = 2,
|
||||
},
|
||||
|
||||
Colors = {
|
||||
White = Color3.new(1, 1, 1),
|
||||
Black = Color3.new(0, 0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
Constants.Layouts = {
|
||||
[Constants.Layout.Small] = SmallLayout,
|
||||
|
||||
[Constants.Layout.Large] = LargeLayout,
|
||||
|
||||
[Constants.Layout.TenFoot] = TenFootLayout,
|
||||
}
|
||||
|
||||
local function makeTableConstant(name, tbl)
|
||||
setmetatable(tbl, {
|
||||
__newindex = function()
|
||||
end,
|
||||
|
||||
__index = function(t, index)
|
||||
error(name.. " table has no index: " ..tostring(index), 2)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local constantTables = {
|
||||
["Constants"] = Constants,
|
||||
|
||||
["Constants.Colors"] = Constants.Colors,
|
||||
["Constants.Layout"] = Constants.Layout,
|
||||
["Constants.LeaveMenuNoSinkInputs"] = Constants.LeaveMenuNoSinkInputs,
|
||||
|
||||
["Constants.LocalizationKeys"] = Constants.LocalizationKeys,
|
||||
["Constants.LocalizationKeys.ErrorMessages"] = Constants.LocalizationKeys.ErrorMessages,
|
||||
|
||||
["Constants.Layouts"] = Constants.Layouts,
|
||||
["Constants.Layouts.Small"] = Constants.Layouts[Constants.Layout.Small],
|
||||
["Constants.Layouts.Large"] = Constants.Layouts[Constants.Layout.Large],
|
||||
["Constants.Layouts.TenFoot"] = Constants.Layouts[Constants.Layout.TenFoot],
|
||||
}
|
||||
|
||||
for name, tbl in pairs(constantTables) do
|
||||
makeTableConstant(name, tbl)
|
||||
end
|
||||
|
||||
return Constants
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
local CoreGui = game:GetService("CoreGui")
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
local Players = game:GetService("Players")
|
||||
local RunService = game:GetService("RunService")
|
||||
local LocalizationService = game:GetService("LocalizationService")
|
||||
local StarterGui = game:GetService("StarterGui")
|
||||
|
||||
local RobloxGui = CoreGui:WaitForChild("RobloxGui")
|
||||
|
||||
local FFlagAllowEmotesMenuWhenTopbarIsDisabled = game:DefineFastFlag("AllowEmotesMenuWhenTopbarIsDisabled", false)
|
||||
local FFlagCoreScriptLoadEmotesFromWeb = settings():GetFFlag("CoreScriptLoadEmotesFromWeb")
|
||||
|
||||
-- Wait for LocalPlayer to exist
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
if not LocalPlayer then
|
||||
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
|
||||
LocalPlayer = Players.LocalPlayer
|
||||
end
|
||||
|
||||
local Roact = require(CorePackages.Roact)
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
local RoactRodux = require(CorePackages.RoactRodux)
|
||||
|
||||
local EmotesModules = script.Parent
|
||||
local CoreScriptModules = EmotesModules.Parent
|
||||
|
||||
local Actions = EmotesModules.Actions
|
||||
local Components = EmotesModules.Components
|
||||
local Reducers = EmotesModules.Reducers
|
||||
local Thunks = EmotesModules.Thunks
|
||||
local Utility = EmotesModules.Utility
|
||||
|
||||
local Backpack = require(CoreScriptModules.BackpackScript)
|
||||
local Chat = require(CoreScriptModules.ChatSelector)
|
||||
local TenFootInterface = require(CoreScriptModules.TenFootInterface)
|
||||
|
||||
local CanPlayEmotes = require(Utility.CanPlayEmotes)
|
||||
local Constants = require(EmotesModules.Constants)
|
||||
|
||||
local EmotesMenu = require(Components.EmotesMenu)
|
||||
local EmotesMenuReducer = require(Reducers.EmotesMenuReducer)
|
||||
|
||||
local LoadEmotesFromWeb = require(Thunks.LoadEmotesFromWeb)
|
||||
local OpenMenu = require(Thunks.OpenMenu)
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
|
||||
local EmotesChanged = require(Actions.EmotesChanged)
|
||||
local EquippedEmotesChanged = require(Actions.EquippedEmotesChanged)
|
||||
local NumberEmotesLoadedChanged = require(Actions.NumberEmotesLoadedChanged)
|
||||
local SetGuiInset = require(Actions.SetGuiInset)
|
||||
local SetLayout = require(Actions.SetLayout)
|
||||
local SetLocale = require(Actions.SetLocale)
|
||||
|
||||
local EmotesMenuMaster = {}
|
||||
EmotesMenuMaster.__index = EmotesMenuMaster
|
||||
|
||||
local function isClient()
|
||||
return RunService:IsClient() and
|
||||
not RunService:IsEdit() and
|
||||
not RunService:IsRunMode()
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:isOpen()
|
||||
return self.store:getState().displayOptions.menuVisible
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:open()
|
||||
self.store:dispatch(OpenMenu())
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:close()
|
||||
self.store:dispatch(HideMenu())
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:setTopBarEnabled(isEnabled)
|
||||
self.topBarEnabled = isEnabled
|
||||
|
||||
if FFlagAllowEmotesMenuWhenTopbarIsDisabled then
|
||||
if not isEnabled then
|
||||
-- Don't allow user to get stuck in a state where the Emotes Menu is stuck open
|
||||
if self:isOpen() then
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
else
|
||||
local isEmotesMenuEnabled = StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.EmotesMenu)
|
||||
|
||||
if isEnabled and isEmotesMenuEnabled and self.canPlayEmotes then
|
||||
self:_mount()
|
||||
else
|
||||
self:_unmount()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_connectCoreGuiListeners()
|
||||
Backpack.StateChanged.Event:Connect(function(isBackpackOpen)
|
||||
if not isBackpackOpen then
|
||||
return
|
||||
end
|
||||
|
||||
if self:isOpen() then
|
||||
self:close()
|
||||
end
|
||||
end)
|
||||
|
||||
Chat.VisibilityStateChanged:connect(function(isChatVisible)
|
||||
if not isChatVisible then
|
||||
return
|
||||
end
|
||||
|
||||
if self:isOpen() then
|
||||
self:close()
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_connectApiListeners()
|
||||
StarterGui.CoreGuiChangedSignal:connect(function(coreGuiType, enabled)
|
||||
if not FFlagAllowEmotesMenuWhenTopbarIsDisabled and not self.topBarEnabled then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.canPlayEmotes then
|
||||
return
|
||||
end
|
||||
|
||||
if coreGuiType == Enum.CoreGuiType.EmotesMenu or coreGuiType == Enum.CoreGuiType.All then
|
||||
if enabled then
|
||||
self:_mount()
|
||||
else
|
||||
self:_unmount()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
GuiService.EmotesMenuOpenChanged:Connect(function(isOpen)
|
||||
if isOpen then
|
||||
if not self:isOpen() then
|
||||
self:open()
|
||||
end
|
||||
else
|
||||
if self:isOpen() then
|
||||
self:close()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_loadEmotesFromWeb()
|
||||
self.store:dispatch(LoadEmotesFromWeb())
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onEquippedEmotesChanged(newEquippedEmotes)
|
||||
self.store:dispatch(EquippedEmotesChanged(newEquippedEmotes))
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onEmotesChanged(newEmotes)
|
||||
self.store:dispatch(EmotesChanged(newEmotes))
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onNumberEmotesLoadedChanged(newNumberEmotesLoaded)
|
||||
self.store:dispatch(NumberEmotesLoadedChanged(newNumberEmotesLoaded))
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onHumanoidDescriptionChanged(humanoidDescription)
|
||||
if self.equippedEmotesChangedConn then
|
||||
self.equippedEmotesChangedConn:Disconnect()
|
||||
self.equippedEmotesChangedConn = nil
|
||||
end
|
||||
|
||||
if self.emotesChangedConn then
|
||||
self.emotesChangedConn:Disconnect()
|
||||
self.emotesChangedConn = nil
|
||||
end
|
||||
|
||||
if self.numberEmotesLoadedChangedConn then
|
||||
self.numberEmotesLoadedChangedConn:Disconnect()
|
||||
self.numberEmotesLoadedChangedConn = nil
|
||||
end
|
||||
|
||||
if self.humanoidDescriptionAncestryConn then
|
||||
self.humanoidDescriptionAncestryConn:Disconnect()
|
||||
self.humanoidDescriptionAncestryConn = nil
|
||||
end
|
||||
|
||||
if humanoidDescription then
|
||||
self.equippedEmotesChangedConn = humanoidDescription.EquippedEmotesChanged:Connect(function(newEquippedEmotes)
|
||||
self:_onEquippedEmotesChanged(newEquippedEmotes)
|
||||
end)
|
||||
self:_onEquippedEmotesChanged(humanoidDescription:GetEquippedEmotes())
|
||||
|
||||
self.emotesChangedConn = humanoidDescription.EmotesChanged:Connect(function(newEmotes)
|
||||
self:_onEmotesChanged(newEmotes)
|
||||
end)
|
||||
|
||||
if FFlagCoreScriptLoadEmotesFromWeb and next(humanoidDescription:GetEmotes()) == nil
|
||||
and humanoidDescription.NumberEmotesLoaded == -1 then
|
||||
self:_loadEmotesFromWeb()
|
||||
else
|
||||
self:_onEmotesChanged(humanoidDescription:GetEmotes())
|
||||
end
|
||||
|
||||
local numberEmotesChangedSignal = humanoidDescription:GetPropertyChangedSignal("NumberEmotesLoaded")
|
||||
self.numberEmotesLoadedChangedConn = numberEmotesChangedSignal:Connect(function(newNumberEmotesLoaded)
|
||||
self:_onNumberEmotesLoadedChanged(newNumberEmotesLoaded)
|
||||
end)
|
||||
self:_onNumberEmotesLoadedChanged(humanoidDescription.NumberEmotesLoaded)
|
||||
|
||||
self.humanoidDescriptionAncestryConn = humanoidDescription.AncestryChanged:Connect(function(child, parent)
|
||||
if child == humanoidDescription and parent == nil then
|
||||
self:_onHumanoidDescriptionChanged(nil)
|
||||
end
|
||||
end)
|
||||
else
|
||||
self:_onEquippedEmotesChanged({})
|
||||
self:_onEmotesChanged({})
|
||||
self:_onNumberEmotesLoadedChanged(-1)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onHumanoidChanged(humanoid)
|
||||
if self.humanoidChildAddedConn then
|
||||
self.humanoidChildAddedConn:Disconnect()
|
||||
self.humanoidChildAddedConn = nil
|
||||
end
|
||||
|
||||
if self.humanoidAncestryChangedConn then
|
||||
self.humanoidAncestryChangedConn:Disconnect()
|
||||
self.humanoidAncestryChangedConn = nil
|
||||
end
|
||||
|
||||
if humanoid then
|
||||
self.humanoidChildAddedConn = humanoid.ChildAdded:Connect(function(child)
|
||||
if child:IsA("HumanoidDescription") then
|
||||
self:_onHumanoidDescriptionChanged(child)
|
||||
end
|
||||
end)
|
||||
|
||||
local humanoidDescription = humanoid:FindFirstChildOfClass("HumanoidDescription")
|
||||
if humanoidDescription then
|
||||
self:_onHumanoidDescriptionChanged(humanoidDescription)
|
||||
elseif FFlagCoreScriptLoadEmotesFromWeb then
|
||||
self:_loadEmotesFromWeb()
|
||||
end
|
||||
|
||||
self.humanoidAncestryChangedConn = humanoid.AncestryChanged:Connect(function(child, parent)
|
||||
if child == humanoid and parent == nil then
|
||||
self:_onHumanoidChanged(nil)
|
||||
end
|
||||
end)
|
||||
else
|
||||
self:_onHumanoidDescriptionChanged(nil)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onCharacterAdded(character)
|
||||
if self.characterChildAddedConn then
|
||||
self.characterChildAddedConn:Disconnect()
|
||||
self.characterChildAddedConn = nil
|
||||
end
|
||||
|
||||
self.characterChildAddedConn = character.ChildAdded:Connect(function(child)
|
||||
if child:IsA("Humanoid") then
|
||||
self:_onHumanoidChanged(child)
|
||||
end
|
||||
end)
|
||||
|
||||
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
|
||||
if humanoid then
|
||||
self:_onHumanoidChanged(humanoid)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_connectListeners()
|
||||
self:_connectCoreGuiListeners()
|
||||
self:_connectApiListeners()
|
||||
|
||||
LocalPlayer.CharacterAdded:Connect(function(character) self:_onCharacterAdded(character) end)
|
||||
if LocalPlayer.Character then
|
||||
self:_onCharacterAdded(LocalPlayer.Character)
|
||||
end
|
||||
|
||||
LocalizationService:GetPropertyChangedSignal("RobloxLocaleId"):Connect(function()
|
||||
self.store:dispatch(SetLocale(LocalizationService.RobloxLocaleId))
|
||||
end)
|
||||
|
||||
CanPlayEmotes.ChangedEvent.Event:connect(function(canPlay)
|
||||
self.canPlayEmotes = canPlay
|
||||
local isEmotesMenuEnabled = StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.EmotesMenu)
|
||||
|
||||
if FFlagAllowEmotesMenuWhenTopbarIsDisabled then
|
||||
if canPlay and isEmotesMenuEnabled then
|
||||
self:_mount()
|
||||
else
|
||||
self:_unmount()
|
||||
end
|
||||
else
|
||||
if canPlay and self.topBarEnabled and isEmotesMenuEnabled then
|
||||
self:_mount()
|
||||
else
|
||||
self:_unmount()
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_onStateChanged(newState, oldState)
|
||||
if oldState.displayOptions.menuVisible ~= newState.displayOptions.menuVisible then
|
||||
self.EmotesMenuToggled:Fire(newState.displayOptions.menuVisible)
|
||||
|
||||
GuiService:SetEmotesMenuOpen(newState.displayOptions.menuVisible)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_mount()
|
||||
if not self.instance then
|
||||
local app = Roact.createElement(RoactRodux.StoreProvider, {
|
||||
store = self.store,
|
||||
}, {
|
||||
EmotesMenu = Roact.createElement(EmotesMenu)
|
||||
})
|
||||
|
||||
self.instance = Roact.mount(app, RobloxGui, "EmotesMenu")
|
||||
|
||||
self.MenuIsVisible = true
|
||||
self.MenuVisibilityChanged:Fire(true)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster:_unmount()
|
||||
if self.instance then
|
||||
Roact.unmount(self.instance)
|
||||
self.instance = nil
|
||||
|
||||
self.MenuIsVisible = false
|
||||
self.MenuVisibilityChanged:Fire(false)
|
||||
end
|
||||
end
|
||||
|
||||
function EmotesMenuMaster.new()
|
||||
local self = setmetatable({}, EmotesMenuMaster)
|
||||
|
||||
self.canPlayEmotes = CanPlayEmotes.Value
|
||||
self.topBarEnabled = true
|
||||
|
||||
-- Bindable that can be used by other modules to see when the Emotes Menu is opened or closed.
|
||||
-- Fired with a bool indicating whether the EmotesMenu is currently open
|
||||
self.EmotesMenuToggled = Instance.new("BindableEvent")
|
||||
|
||||
-- Indicates whether the menu is currently mounted
|
||||
self.MenuIsVisible = false
|
||||
|
||||
-- Bindable that can be used by other modules to see when the Emotes Menu should
|
||||
-- not be visible to users or should be visible to users.
|
||||
self.MenuVisibilityChanged = Instance.new("BindableEvent")
|
||||
|
||||
self.store = Rodux.Store.new(EmotesMenuReducer, {}, {
|
||||
Rodux.thunkMiddleware,
|
||||
})
|
||||
|
||||
-- If not running in the correct context return early and don't mount the Gui
|
||||
-- This is necessary for non-networked DataModels where CoreScripts can still run
|
||||
if not isClient() then
|
||||
return self
|
||||
end
|
||||
|
||||
self.store.changed:connect(function(newState, oldState)
|
||||
self:_onStateChanged(newState, oldState)
|
||||
end)
|
||||
|
||||
self.store:dispatch(SetLocale(LocalizationService.RobloxLocaleId))
|
||||
|
||||
if TenFootInterface:IsEnabled() then
|
||||
self.store:dispatch(SetLayout(Constants.Layout.TenFoot))
|
||||
end
|
||||
|
||||
local inGameGlobalGuiInset = settings():GetFVariable("InGameGlobalGuiInset")
|
||||
if not RobloxGui.IgnoreGuiInset then
|
||||
self.store:dispatch(SetGuiInset(inGameGlobalGuiInset))
|
||||
end
|
||||
|
||||
self:_mount()
|
||||
|
||||
self:_connectListeners()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return EmotesMenuMaster.new()
|
||||
@@ -0,0 +1,34 @@
|
||||
return {
|
||||
EmotesWheelMinSize = Vector2.new(200, 200),
|
||||
EmotesWheelMaxSize = Vector2.new(400, 400),
|
||||
|
||||
ErrorFrameSize = UDim2.new(0.6, 0, 0.2, 0),
|
||||
ErrorFramePosition = UDim2.new(0, 0, 0.1, 0),
|
||||
|
||||
ErrorFrameAspectRatio = 6,
|
||||
ErrorFrameMinSize = Vector2.new(336, 56),
|
||||
ErrorFrameMaxSize = Vector2.new(432, 72),
|
||||
|
||||
ErrorTextFont = Enum.Font.Gotham,
|
||||
ErrorTextSize = 36,
|
||||
|
||||
ErrorIcon = "rbxasset://textures/ui/Emotes/ErrorIcon.png",
|
||||
ErrorIconOffset = 10,
|
||||
|
||||
MiddleTextSize = 28,
|
||||
MiddleTextFont = Enum.Font.Gotham,
|
||||
|
||||
SlotNumberTextSize = 24,
|
||||
SlotNumberFont = Enum.Font.Gotham,
|
||||
|
||||
CircleBackground = "rbxasset://textures/ui/Emotes/Large/CircleBackground.png",
|
||||
|
||||
SegmentedCircle = "rbxasset://textures/ui/Emotes/Large/SegmentedCircle.png",
|
||||
|
||||
SelectedLine = "rbxasset://textures/ui/Emotes/Large/SelectedLine.png",
|
||||
SelectedLineImageSize = Vector2.new(11, 67),
|
||||
SelectedLinePadding = 4,
|
||||
|
||||
SelectedGradient = "rbxasset://textures/ui/Emotes/Large/SelectedGradient.png",
|
||||
SelectedGradientImageSize = Vector2.new(183, 141),
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
return {
|
||||
EmotesWheelMinSize = Vector2.new(150, 150),
|
||||
EmotesWheelMaxSize = Vector2.new(300, 300),
|
||||
|
||||
ErrorFrameSize = UDim2.new(0.6, 0, 0.2, 0),
|
||||
ErrorFramePosition = UDim2.new(0, 0, 0.1, 0),
|
||||
|
||||
ErrorFrameAspectRatio = 6,
|
||||
ErrorFrameMinSize = Vector2.new(336, 56),
|
||||
ErrorFrameMaxSize = Vector2.new(432, 72),
|
||||
|
||||
ErrorTextFont = Enum.Font.Gotham,
|
||||
ErrorTextSize = 24,
|
||||
|
||||
ErrorIcon = "rbxasset://textures/ui/Emotes/ErrorIcon.png",
|
||||
ErrorIconOffset = 10,
|
||||
|
||||
MiddleTextSize = 18,
|
||||
MiddleTextFont = Enum.Font.Gotham,
|
||||
|
||||
SlotNumberTextSize = 16,
|
||||
SlotNumberFont = Enum.Font.Gotham,
|
||||
|
||||
CircleBackground = "rbxasset://textures/ui/Emotes/Small/CircleBackground.png",
|
||||
|
||||
SegmentedCircle = "rbxasset://textures/ui/Emotes/Small/SegmentedCircle.png",
|
||||
|
||||
SelectedLine = "rbxasset://textures/ui/Emotes/Small/SelectedLine.png",
|
||||
SelectedLineImageSize = Vector2.new(8, 50),
|
||||
SelectedLinePadding = 2,
|
||||
|
||||
SelectedGradient = "rbxasset://textures/ui/Emotes/Small/SelectedGradient.png",
|
||||
SelectedGradientImageSize = Vector2.new(138, 106),
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
return {
|
||||
EmotesWheelMinSize = Vector2.new(320, 320),
|
||||
EmotesWheelMaxSize = Vector2.new(640, 640),
|
||||
|
||||
ErrorFrameSize = UDim2.new(0.6, 0, 0.2, 0),
|
||||
ErrorFramePosition = UDim2.new(0, 0, 0.1, 0),
|
||||
|
||||
ErrorFrameAspectRatio = 6,
|
||||
ErrorFrameMinSize = Vector2.new(336, 56),
|
||||
ErrorFrameMaxSize = Vector2.new(432, 72),
|
||||
|
||||
ErrorTextFont = Enum.Font.Gotham,
|
||||
ErrorTextSize = 48,
|
||||
|
||||
ErrorIcon = "rbxasset://textures/ui/Emotes/ErrorIcon.png",
|
||||
ErrorIconOffset = 10,
|
||||
|
||||
MiddleTextSize = 48,
|
||||
MiddleTextFont = Enum.Font.Gotham,
|
||||
|
||||
SlotNumberTextSize = 48,
|
||||
SlotNumberFont = Enum.Font.Gotham,
|
||||
|
||||
CircleBackground = "rbxasset://textures/ui/Emotes/TenFoot/CircleBackground.png",
|
||||
|
||||
SegmentedCircle = "rbxasset://textures/ui/Emotes/TenFoot/SegmentedCircle.png",
|
||||
|
||||
SelectedLine = "rbxasset://textures/ui/Emotes/TenFoot/SelectedLine.png",
|
||||
SelectedLineImageSize = Vector2.new(18, 107),
|
||||
SelectedLinePadding = 5,
|
||||
|
||||
SelectedGradient = "rbxasset://textures/ui/Emotes/TenFoot/SelectedGradient.png",
|
||||
SelectedGradientImageSize = Vector2.new(292, 225),
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
local EmotesMenu = Reducers.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local ShowMenu = require(Actions.ShowMenu)
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
|
||||
local ShowError = require(Actions.ShowError)
|
||||
local HideError = require(Actions.HideError)
|
||||
|
||||
local SetGuiInset = require(Actions.SetGuiInset)
|
||||
|
||||
local default = {
|
||||
menuVisible = false,
|
||||
|
||||
guiInset = 0,
|
||||
|
||||
errorVisible = false,
|
||||
errorText = "",
|
||||
}
|
||||
|
||||
return Rodux.createReducer(default, {
|
||||
[ShowMenu.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
menuVisible = true,
|
||||
})
|
||||
end,
|
||||
|
||||
[HideMenu.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
menuVisible = false,
|
||||
})
|
||||
end,
|
||||
|
||||
[ShowError.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
errorVisible = true,
|
||||
errorText = action.errorText,
|
||||
})
|
||||
end,
|
||||
|
||||
[HideError.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
errorVisible = false,
|
||||
})
|
||||
end,
|
||||
|
||||
[SetGuiInset.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
guiInset = action.guiInset,
|
||||
})
|
||||
end,
|
||||
})
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
|
||||
local DisplayOptions = require(Reducers.DisplayOptions)
|
||||
local EmotesPage = require(Reducers.EmotesPage)
|
||||
local EmotesWheel = require(Reducers.EmotesWheel)
|
||||
local Layout = require(Reducers.Layout)
|
||||
local Locale = require(Reducers.Locale)
|
||||
|
||||
local EmotesMenuReducer = Rodux.combineReducers({
|
||||
displayOptions = DisplayOptions,
|
||||
emotesPage = EmotesPage,
|
||||
emotesWheel = EmotesWheel,
|
||||
layout = Layout,
|
||||
locale = Locale,
|
||||
})
|
||||
|
||||
return EmotesMenuReducer
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
local EmotesMenu = Reducers.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local EmotesChanged = require(Actions.EmotesChanged)
|
||||
local EquippedEmotesChanged = require(Actions.EquippedEmotesChanged)
|
||||
local NumberEmotesLoadedChanged = require(Actions.NumberEmotesLoadedChanged)
|
||||
|
||||
local default = {
|
||||
emotesInfo = {},
|
||||
equippedEmotes = {},
|
||||
|
||||
numberEmotesLoaded = -1,
|
||||
currentEmotes = {},
|
||||
}
|
||||
|
||||
local function createCurrentEmotes(emotesInfoTable, equippedEmotesTable)
|
||||
local currentEmotes = {}
|
||||
|
||||
for _, emoteInfo in ipairs(equippedEmotesTable) do
|
||||
local slot = emoteInfo.Slot
|
||||
local emoteName = emoteInfo.Name
|
||||
|
||||
if slot and emoteName then
|
||||
if emotesInfoTable[emoteName] then
|
||||
currentEmotes[slot] = emoteName
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return currentEmotes
|
||||
end
|
||||
|
||||
return Rodux.createReducer(default, {
|
||||
[EmotesChanged.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
emotesInfo = action.emotes,
|
||||
currentEmotes = createCurrentEmotes(action.emotes, state.equippedEmotes),
|
||||
})
|
||||
end,
|
||||
|
||||
[EquippedEmotesChanged.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
equippedEmotes = action.equippedEmotes,
|
||||
currentEmotes = createCurrentEmotes(state.emotesInfo, action.equippedEmotes),
|
||||
})
|
||||
end,
|
||||
|
||||
[NumberEmotesLoadedChanged.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
numberEmotesLoaded = action.numberEmotesLoaded,
|
||||
})
|
||||
end,
|
||||
})
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Cryo = require(CorePackages.Cryo)
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
local EmotesMenu = Reducers.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
local FocusSegment = require(Actions.FocusSegment)
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
|
||||
local default = {
|
||||
focusedSegmentIndex = 0,
|
||||
}
|
||||
|
||||
return Rodux.createReducer(default, {
|
||||
[FocusSegment.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, {
|
||||
focusedSegmentIndex = action.segmentIndex,
|
||||
})
|
||||
end,
|
||||
|
||||
-- Reset EmotesWheel state when the menu is hidden
|
||||
[HideMenu.name] = function(state, action)
|
||||
return Cryo.Dictionary.join(state, default)
|
||||
end,
|
||||
})
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
local EmotesMenu = Reducers.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local SetLayout = require(Actions.SetLayout)
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
return Rodux.createReducer(Constants.Layout.Large, {
|
||||
[SetLayout.name] = function(state, action)
|
||||
return action.layout
|
||||
end,
|
||||
})
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
|
||||
local Rodux = require(CorePackages.Rodux)
|
||||
|
||||
local Reducers = script.Parent
|
||||
local EmotesMenu = Reducers.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local SetLocale = require(Actions.SetLocale)
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
return Rodux.createReducer(Constants.FallbackLocale, {
|
||||
[SetLocale.name] = function(state, action)
|
||||
return action.locale
|
||||
end,
|
||||
})
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local HttpRbxApiService = game:GetService("HttpRbxApiService")
|
||||
local HttpService = game:GetService("HttpService")
|
||||
local StarterPlayer = game:GetService("StarterPlayer")
|
||||
|
||||
local Thunks = script.Parent
|
||||
local EmotesMenu = Thunks.Parent
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local Url = require(CorePackages.AppTempCommon.LuaApp.Http.Url)
|
||||
|
||||
local EmotesChanged = require(Actions.EmotesChanged)
|
||||
local EquippedEmotesChanged = require(Actions.EquippedEmotesChanged)
|
||||
|
||||
local cachedEmotes = nil
|
||||
|
||||
local function getEmotesInfo()
|
||||
if cachedEmotes then
|
||||
return cachedEmotes
|
||||
end
|
||||
|
||||
local getEmotesUrl = Url.AVATAR_URL .. "v1/emotes"
|
||||
|
||||
local success, result = pcall(HttpRbxApiService.GetAsyncFullUrl, HttpRbxApiService, getEmotesUrl)
|
||||
if not success then
|
||||
return {}
|
||||
end
|
||||
|
||||
success, result = pcall(HttpService.JSONDecode, HttpService, result)
|
||||
if not success then
|
||||
return {}
|
||||
end
|
||||
|
||||
cachedEmotes = result
|
||||
return result
|
||||
end
|
||||
|
||||
local function loadEmotes(store)
|
||||
local equippedEmotesInfo = getEmotesInfo()
|
||||
|
||||
local emotesTable = {}
|
||||
local equippedEmotesTable = {}
|
||||
|
||||
for index, emoteInfo in ipairs(equippedEmotesInfo) do
|
||||
emotesTable[emoteInfo.assetName] = { emoteInfo.assetId }
|
||||
equippedEmotesTable[index] = {
|
||||
Slot = emoteInfo.position,
|
||||
Name = emoteInfo.assetName,
|
||||
}
|
||||
end
|
||||
|
||||
store:dispatch(EmotesChanged(emotesTable))
|
||||
store:dispatch(EquippedEmotesChanged(equippedEmotesTable))
|
||||
end
|
||||
|
||||
local function LoadEmotesFromWeb()
|
||||
return function(store)
|
||||
if not StarterPlayer.UserEmotesEnabled then
|
||||
return
|
||||
end
|
||||
|
||||
coroutine.wrap(loadEmotes)(store)
|
||||
end
|
||||
end
|
||||
|
||||
return LoadEmotesFromWeb
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local GuiService = game:GetService("GuiService")
|
||||
|
||||
local Thunks = script.Parent
|
||||
local EmotesMenu = Thunks.Parent
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local CoreScriptModules = EmotesMenu.Parent
|
||||
|
||||
local EventStream = require(CorePackages.AppTempCommon.Temp.EventStream)
|
||||
|
||||
local Analytics = require(EmotesMenu.Analytics)
|
||||
local Backpack = require(CoreScriptModules.BackpackScript)
|
||||
local ShowMenu = require(Actions.ShowMenu)
|
||||
|
||||
local EmotesAnalytics = Analytics.new():withEventStream(EventStream.new())
|
||||
|
||||
local function OpenMenu(emoteName)
|
||||
return function(store)
|
||||
if GuiService.MenuIsOpen then
|
||||
return
|
||||
end
|
||||
|
||||
if Backpack.IsOpen then
|
||||
Backpack.OpenClose()
|
||||
end
|
||||
|
||||
-- If user is interacting with the backpack it can stay open
|
||||
if Backpack.IsOpen then
|
||||
return
|
||||
end
|
||||
|
||||
EmotesAnalytics:onMenuOpened()
|
||||
|
||||
-- Backpack was closed, show the emotes menu
|
||||
store:dispatch(ShowMenu())
|
||||
end
|
||||
end
|
||||
|
||||
return OpenMenu
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
local CorePackages = game:GetService("CorePackages")
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local FFlagCoreScriptLoadEmotesFromWeb = settings():GetFFlag("CoreScriptLoadEmotesFromWeb")
|
||||
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
|
||||
local Thunks = script.Parent
|
||||
local EmotesMenu = Thunks.Parent
|
||||
local CoreScriptModules = EmotesMenu.Parent
|
||||
|
||||
local Actions = EmotesMenu.Actions
|
||||
|
||||
local Analytics = require(EmotesMenu.Analytics)
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
local EventStream = require(CorePackages.AppTempCommon.Temp.EventStream)
|
||||
local RobloxTranslator = require(CoreScriptModules.RobloxTranslator)
|
||||
|
||||
local HideMenu = require(Actions.HideMenu)
|
||||
local HideError = require(Actions.HideError)
|
||||
local ShowError = require(Actions.ShowError)
|
||||
|
||||
local EmotesAnalytics = Analytics.new():withEventStream(EventStream.new())
|
||||
|
||||
local function emoteInHumanoidDescription(humanoidDescription, emoteName)
|
||||
if not humanoidDescription then
|
||||
return false
|
||||
end
|
||||
|
||||
local emotes = humanoidDescription:GetEmotes()
|
||||
if emotes and emotes[emoteName] then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function handlePlayFailure(store, reasonLocalizationKey)
|
||||
if reasonLocalizationKey then
|
||||
local locale = store:getState().locale
|
||||
local reason = RobloxTranslator:FormatByKeyForLocale(reasonLocalizationKey, locale)
|
||||
|
||||
store:dispatch(ShowError(reason))
|
||||
delay(Constants.ErrorDisplayTimeSeconds, function()
|
||||
store:dispatch(HideError())
|
||||
end)
|
||||
end
|
||||
|
||||
store:dispatch(HideMenu())
|
||||
end
|
||||
|
||||
local function PlayEmote(emoteName, slotNumber, emoteAssetId)
|
||||
return function(store)
|
||||
local character = LocalPlayer.Character
|
||||
if not character then
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.TemporarilyUnavailable)
|
||||
return
|
||||
end
|
||||
|
||||
local humanoid = character:FindFirstChildOfClass("Humanoid")
|
||||
if not humanoid then
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.TemporarilyUnavailable)
|
||||
return
|
||||
end
|
||||
|
||||
if humanoid.RigType ~= Enum.HumanoidRigType.R15 then
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.R15Only)
|
||||
return
|
||||
end
|
||||
|
||||
local animate = character:FindFirstChild("Animate")
|
||||
if not animate then
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.NotSupported)
|
||||
return
|
||||
end
|
||||
|
||||
local humanoidDescription = humanoid:FindFirstChildOfClass("HumanoidDescription")
|
||||
if not FFlagCoreScriptLoadEmotesFromWeb and not humanoidDescription then
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.NotSupported)
|
||||
return
|
||||
end
|
||||
|
||||
local playEmoteBindable = animate:FindFirstChild("PlayEmote")
|
||||
if playEmoteBindable and playEmoteBindable:IsA("BindableFunction") then
|
||||
store:dispatch(HideMenu())
|
||||
|
||||
local playEmoteFunction = function() return humanoid:PlayEmote(emoteName) end
|
||||
if FFlagCoreScriptLoadEmotesFromWeb and not emoteInHumanoidDescription(humanoidDescription, emoteName) then
|
||||
playEmoteFunction = function() return humanoid:PlayEmoteById(emoteName, emoteAssetId) end
|
||||
end
|
||||
|
||||
local success, didPlay = pcall(playEmoteFunction)
|
||||
|
||||
if success and didPlay then
|
||||
EmotesAnalytics:onEmotePlayed(slotNumber, emoteAssetId)
|
||||
else
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.TemporarilyUnavailable)
|
||||
return
|
||||
end
|
||||
else
|
||||
handlePlayFailure(store, Constants.LocalizationKeys.ErrorMessages.NotSupported)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return PlayEmote
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
local Players = game:GetService("Players")
|
||||
|
||||
local LocalPlayer = Players.LocalPlayer
|
||||
if not LocalPlayer then
|
||||
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
|
||||
LocalPlayer = Players.LocalPlayer
|
||||
end
|
||||
|
||||
local CanPlayEmotes = {
|
||||
Value = false,
|
||||
ChangedEvent = Instance.new("BindableEvent")
|
||||
}
|
||||
|
||||
local function getAnimateScript(character)
|
||||
if not character then
|
||||
return nil
|
||||
end
|
||||
|
||||
local animate = character:FindFirstChild("Animate")
|
||||
if animate and animate:IsA("LocalScript") then
|
||||
return animate
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getPlayEmoteBindable(character)
|
||||
local animate = getAnimateScript(character)
|
||||
if not animate then
|
||||
return nil
|
||||
end
|
||||
|
||||
local playEmoteBindable = animate:FindFirstChild("PlayEmote")
|
||||
if playEmoteBindable and playEmoteBindable:IsA("BindableFunction") then
|
||||
return playEmoteBindable
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
local function getCanPlayEmotes(character)
|
||||
local playEmoteBindable = getPlayEmoteBindable(character)
|
||||
return playEmoteBindable ~= nil
|
||||
end
|
||||
|
||||
local animateAddedListener, playEmoteAddedListener, playEmoteRemovedListener
|
||||
|
||||
local function disconnectListeners()
|
||||
if animateAddedListener then
|
||||
animateAddedListener:Disconnect()
|
||||
animateAddedListener = nil
|
||||
end
|
||||
|
||||
if playEmoteAddedListener then
|
||||
playEmoteAddedListener:Disconnect()
|
||||
playEmoteAddedListener = nil
|
||||
end
|
||||
|
||||
if playEmoteRemovedListener then
|
||||
playEmoteRemovedListener:Disconnect()
|
||||
playEmoteRemovedListener = nil
|
||||
end
|
||||
end
|
||||
|
||||
local checkConnect
|
||||
|
||||
local function checkUpdate(character)
|
||||
local newCanPlayEmotes = getCanPlayEmotes(character)
|
||||
|
||||
if CanPlayEmotes.Value ~= newCanPlayEmotes then
|
||||
CanPlayEmotes.Value = newCanPlayEmotes
|
||||
CanPlayEmotes.ChangedEvent:Fire(newCanPlayEmotes)
|
||||
end
|
||||
|
||||
checkConnect(newCanPlayEmotes, character)
|
||||
end
|
||||
|
||||
local function connectAnimateAddedListener(character)
|
||||
local animate = getAnimateScript(character)
|
||||
if animate then
|
||||
checkUpdate(character)
|
||||
return
|
||||
end
|
||||
|
||||
animateAddedListener = character.ChildAdded:Connect(function(child)
|
||||
if child:IsA("LocalScript") and child.Name == "Animate" then
|
||||
checkUpdate(character)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function connectPlayEmoteAddedListener(character, animateScript)
|
||||
local playEmoteBindable = getPlayEmoteBindable(character)
|
||||
if playEmoteBindable then
|
||||
checkUpdate(character)
|
||||
return
|
||||
end
|
||||
|
||||
playEmoteAddedListener = animateScript.ChildAdded:Connect(function(child)
|
||||
if child:IsA("BindableFunction") and child.Name == "PlayEmote" then
|
||||
checkUpdate(character)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function connectPlayEmoteRemovedListener(character)
|
||||
local playEmoteBindable = getPlayEmoteBindable(character)
|
||||
if not playEmoteBindable then
|
||||
checkUpdate(character)
|
||||
return
|
||||
end
|
||||
|
||||
playEmoteRemovedListener = playEmoteBindable.AncestryChanged:Connect(function(_, parent)
|
||||
if not parent or not playEmoteBindable:IsDescendantOf(character) then
|
||||
checkUpdate(character)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
checkConnect = function(canPlay, character)
|
||||
disconnectListeners()
|
||||
|
||||
if not character then
|
||||
return
|
||||
end
|
||||
|
||||
if canPlay then
|
||||
connectPlayEmoteRemovedListener(character)
|
||||
else
|
||||
local animateScript = getAnimateScript(character)
|
||||
|
||||
if animateScript then
|
||||
connectPlayEmoteAddedListener(character, animateScript)
|
||||
else
|
||||
connectAnimateAddedListener(character)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function onCharacterChanged(character)
|
||||
disconnectListeners()
|
||||
checkUpdate(character)
|
||||
end
|
||||
|
||||
LocalPlayer:GetPropertyChangedSignal("Character"):Connect(function()
|
||||
onCharacterChanged(LocalPlayer.Character)
|
||||
end)
|
||||
onCharacterChanged(LocalPlayer.Character)
|
||||
|
||||
return CanPlayEmotes
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
local Utility = script.Parent
|
||||
local EmotesMenu = Utility.Parent
|
||||
|
||||
local Constants = require(EmotesMenu.Constants)
|
||||
|
||||
local function GetSegmentIndexFromPosition(position)
|
||||
local angle = math.deg(math.atan2(position.X, position.Y))
|
||||
if angle < 0 then
|
||||
angle = angle + 360
|
||||
end
|
||||
|
||||
local segmentAngle = 360 / Constants.EmotesPerPage
|
||||
angle = angle + math.ceil(segmentAngle / 2)
|
||||
angle = math.fmod(angle, 360)
|
||||
|
||||
local segmentIndex = math.ceil(angle / segmentAngle)
|
||||
|
||||
local offsetAngle = Constants.SegmentsStartRotation + 90
|
||||
local segmentOffset = offsetAngle / segmentAngle
|
||||
|
||||
segmentIndex = segmentIndex + segmentOffset
|
||||
segmentIndex = math.ceil(segmentIndex)
|
||||
|
||||
if segmentIndex > Constants.EmotesPerPage then
|
||||
segmentIndex = segmentIndex - Constants.EmotesPerPage
|
||||
elseif segmentIndex < 1 then
|
||||
segmentIndex = segmentIndex + Constants.EmotesPerPage
|
||||
end
|
||||
|
||||
return segmentIndex
|
||||
end
|
||||
|
||||
return GetSegmentIndexFromPosition
|
||||
Reference in New Issue
Block a user