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 FFlagEmotesMenuShowUiOnlyWhenAvailable = game:DefineFastFlag("EmotesMenuShowUiOnlyWhenAvailable", false) 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 if FFlagEmotesMenuShowUiOnlyWhenAvailable then CanPlayEmotes = require(Utility.CanPlayEmotes) end 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 FFlagCoreScriptBetterEmotesErrorMessaging = settings():GetFFlag("CoreScriptBetterEmotesErrorMessaging") 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 (not FFlagEmotesMenuShowUiOnlyWhenAvailable or (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 FFlagEmotesMenuShowUiOnlyWhenAvailable and 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 FFlagCoreScriptBetterEmotesErrorMessaging then if self.numberEmotesLoadedChangedConn then self.numberEmotesLoadedChangedConn:Disconnect() self.numberEmotesLoadedChangedConn = nil end 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 if FFlagCoreScriptBetterEmotesErrorMessaging then local numberEmotesChangedSignal = humanoidDescription:GetPropertyChangedSignal("NumberEmotesLoaded") self.numberEmotesLoadedChangedConn = numberEmotesChangedSignal:Connect(function(newNumberEmotesLoaded) self:_onNumberEmotesLoadedChanged(newNumberEmotesLoaded) end) self:_onNumberEmotesLoadedChanged(humanoidDescription.NumberEmotesLoaded) end 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({}) if FFlagCoreScriptBetterEmotesErrorMessaging then self:_onNumberEmotesLoadedChanged(-1) end 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) if FFlagEmotesMenuShowUiOnlyWhenAvailable then 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 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") if FFlagEmotesMenuShowUiOnlyWhenAvailable then self.MenuIsVisible = true self.MenuVisibilityChanged:Fire(true) end end end function EmotesMenuMaster:_unmount() if self.instance then Roact.unmount(self.instance) self.instance = nil if FFlagEmotesMenuShowUiOnlyWhenAvailable then self.MenuIsVisible = false self.MenuVisibilityChanged:Fire(false) end end end function EmotesMenuMaster.new() local self = setmetatable({}, EmotesMenuMaster) -- 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 if FFlagEmotesMenuShowUiOnlyWhenAvailable then self.canPlayEmotes = CanPlayEmotes.Value end 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") if FFlagEmotesMenuShowUiOnlyWhenAvailable then -- 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") end self.store = Rodux.Store.new(EmotesMenuReducer, {}, { Rodux.thunkMiddleware, }) 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()