--[[ // FileName: PlayerlistModule.lua // Version 1.3 // Written by: jmargh // Description: Implementation of in game player list and leaderboard ]] local CoreGui = game:GetService('CoreGui') local GuiService = game:GetService('GuiService') -- NOTE: Can only use in core scripts local UserInputService = game:GetService('UserInputService') local HttpService = game:GetService('HttpService') local HttpRbxApiService = game:GetService('HttpRbxApiService') local Players = game:GetService('Players') local TeamsService = game:FindService('Teams') local ContextActionService = game:GetService('ContextActionService') local StarterGui = game:GetService('StarterGui') local RbxGuiLibrary = nil if LoadLibrary then RbxGuiLibrary = LoadLibrary("RbxGui") end while not Players.LocalPlayer do wait() end local Player = Players.LocalPlayer local RobloxGui = CoreGui:WaitForChild('RobloxGui') RobloxGui:WaitForChild("Modules"):WaitForChild("TenFootInterface") local TenFootInterface = require(RobloxGui.Modules.TenFootInterface) local isTenFootInterface = TenFootInterface:IsEnabled() local playerDropDownModule = require(RobloxGui.Modules:WaitForChild("PlayerDropDown")) local blockingUtility = playerDropDownModule:CreateBlockingUtility() local playerDropDown = playerDropDownModule:CreatePlayerDropDown() --[[ Fast Flags ]]-- local followerSuccess, isFollowersEnabled = pcall(function() return settings():GetFFlag("EnableLuaFollowers") end) local IsFollowersEnabled = followerSuccess and isFollowersEnabled local serverFollowersSuccess, serverFollowersEnabled = pcall(function() return settings():GetFFlag("UserServerFollowers") end) local IsServerFollowers = serverFollowersSuccess and serverFollowersEnabled --[[ Remotes ]]-- local RemoveEvent_OnFollowRelationshipChanged = nil local RemoteFunc_GetFollowRelationships = nil --[[ Start Module ]]-- local Playerlist = {} --[[ Public Event API ]]-- -- Parameters: Sorted Array - see GameStats below Playerlist.OnLeaderstatsChanged = Instance.new('BindableEvent') -- Parameters: nameOfStat(string), formatedStringOfStat(string) Playerlist.OnStatChanged = Instance.new('BindableEvent') --[[ Client Stat Table ]]-- -- Sorted Array of tables local GameStats = {} -- Fields -- Name: String the developer has given the stat -- Text: Formated string of the stat value -- AddId: Child add order id -- IsPrimary: Is this the primary stat -- Priority: Sorting priority -- NOTE: IsPrimary and Priority are unofficially supported. They are left over legacy from the old player list. -- They can be un-supported at anytime. You should prefer using child add order to order your stats in the leader board. --[[ Script Variables ]]-- local topbarEnabled = true local playerlistCoreGuiEnabled = true local MyPlayerEntryTopFrame = nil local PlayerEntries = {} local StatAddId = 0 local TeamEntries = {} local TeamAddId = 0 local NeutralTeam = nil local IsShowingNeutralFrame = false local LastSelectedFrame = nil local LastSelectedPlayer = nil local MinContainerSize = UDim2.new(0, 165, 0.5, 0) if isTenFootInterface then MinContainerSize = UDim2.new(0, 1000, 0, 720) end local TempHideKeys = {} local PlayerEntrySizeY = 24 if isTenFootInterface then PlayerEntrySizeY = 80 end local TeamEntrySizeY = 18 if isTenFootInterface then TeamEntrySizeY = 32 end local NameEntrySizeX = 170 if isTenFootInterface then NameEntrySizeX = 350 end local StatEntrySizeX = 75 if isTenFootInterface then StatEntrySizeX = 250 end local IsSmallScreenDevice = UserInputService.TouchEnabled and GuiService:GetScreenResolution().Y <= 500 local BaseUrl = game:GetService('ContentProvider').BaseUrl:lower() BaseUrl = string.gsub(BaseUrl, "/m.", "/www.") AssetGameUrl = string.gsub(BaseUrl, 'www', 'assetgame') --[[ Constants ]]-- local ENTRY_PAD = 2 local BG_TRANSPARENCY = 0.5 local BG_COLOR = Color3.new(31/255, 31/255, 31/255) local BG_COLOR_TOP = Color3.new(106/255, 106/255, 106/255) local TEXT_STROKE_TRANSPARENCY = 0.75 local TEXT_COLOR = Color3.new(1, 1, 243/255) local TEXT_STROKE_COLOR = Color3.new(34/255, 34/255, 34/255) local TWEEN_TIME = 0.15 local MAX_LEADERSTATS = 4 local MAX_STR_LEN = 12 local TILE_SPACING = 2 if isTenFootInterface then BG_COLOR_TOP = Color3.new(25/255, 25/255, 25/255) BG_COLOR = Color3.new(60/255, 60/255, 60/255) BG_TRANSPARENCY = 0.25 TEXT_STROKE_TRANSPARENCY = 1 TILE_SPACING = 5 end local SHADOW_IMAGE = 'ayaasset://textures/ui/PlayerList/TileShadowMissingTop.png' local SHADOW_SLICE_SIZE = 5 local SHADOW_SLICE_RECT = Rect.new(SHADOW_SLICE_SIZE+1, SHADOW_SLICE_SIZE+1, SHADOW_SLICE_SIZE*2-1, SHADOW_SLICE_SIZE*2-1) local ADMINS = { -- Admins with special icons ['7210880'] = 'https://assetdelivery.roblox.com/v1/asset?id=134032333', -- Jeditkacheff ['13268404'] = 'https://assetdelivery.roblox.com/v1/asset?id=113059239', -- Sorcus ['261'] = 'https://assetdelivery.roblox.com/v1/asset?id=105897927', -- shedlestky ['20396599'] = 'https://assetdelivery.roblox.com/v1/asset?id=161078086', -- Robloxsai } local ABUSES = { "Swearing", "Bullying", "Scamming", "Dating", "Cheating/Exploiting", "Personal Questions", "Offsite Links", "Bad Username", } local FOLLOWER_STATUS = { FOLLOWER = 0, FOLLOWING = 1, MUTUAL = 2, } --[[ Images ]]-- local CHAT_ICON = 'ayaasset://textures/ui/chat_teamButton.png' local ADMIN_ICON = 'ayaasset://textures/Kiseki.png' local PLACE_OWNER_ICON = 'ayaasset://textures/ui/icon_placeowner.png' local BC_ICON = 'ayaasset://textures/ui/icon_BC-16.png' local TBC_ICON = 'ayaasset://textures/ui/icon_TBC-16.png' local OBC_ICON = 'ayaasset://textures/ui/icon_OBC-16.png' local BLOCKED_ICON = 'ayaasset://textures/ui/PlayerList/BlockedIcon.png' local FRIEND_ICON = 'ayaasset://textures/ui/icon_friends_16.png' local FRIEND_REQUEST_ICON = 'ayaasset://textures/ui/icon_friendrequestsent_16.png' local FRIEND_RECEIVED_ICON = 'ayaasset://textures/ui/icon_friendrequestrecieved-16.png' local FOLLOWER_ICON = 'ayaasset://textures/ui/icon_follower-16.png' local FOLLOWING_ICON = 'ayaasset://textures/ui/icon_following-16.png' local MUTUAL_FOLLOWING_ICON = 'ayaasset://textures/ui/icon_mutualfollowing-16.png' local CHARACTER_BACKGROUND_IMAGE = 'ayaasset://textures/ui/PlayerList/CharacterBackgroundImage.png' --[[ Helper Functions ]]-- local function clamp(value, min, max) if value < min then value = min elseif value > max then value = max end return value end -- Returns whether followerUserId is following userId local function isFollowing(userId, followerUserId) local apiPath = "user/following-exists?userId=" local params = userId.."&followerUserId="..followerUserId local success, result = pcall(function() return HttpRbxApiService:GetAsync(apiPath..params, true) end) if not success then print("isFollowing() failed because", result) return false end -- can now parse web response result = HttpService:JSONDecode(result) return result["success"] and result["isFollowing"] end -- TODO: Once server followers is good to go, remove this function and all code paths local function getFollowerStatus(selectedPlayer) -- we're going to check this flag first in case of a condition were the two flags are not set in sync -- in that case, followers will be disabled if not IsFollowersEnabled then return nil end if selectedPlayer == Player then return nil end -- ignore guest if selectedPlayer.userId <= 0 or Player.userId <= 0 then return end local myUserId = tostring(Player.userId) local theirUserId = tostring(selectedPlayer.userId) local isFollowingMe = isFollowing(myUserId, theirUserId) local isFollowingThem = isFollowing(theirUserId, myUserId) if isFollowingMe and isFollowingThem then -- mutual return FOLLOWER_STATUS.MUTUAL elseif isFollowingMe then return FOLLOWER_STATUS.FOLLOWER elseif isFollowingThem then return FOLLOWER_STATUS.FOLLOWING else return nil end end local function getFriendStatusIcon(friendStatus) if friendStatus == Enum.FriendStatus.Unknown or friendStatus == Enum.FriendStatus.NotFriend then return nil elseif friendStatus == Enum.FriendStatus.Friend then return FRIEND_ICON elseif friendStatus == Enum.FriendStatus.FriendRequestSent then return FRIEND_REQUEST_ICON elseif friendStatus == Enum.FriendStatus.FriendRequestReceived then return FRIEND_RECEIVED_ICON else error("PlayerList: Unknown value for friendStatus: "..tostring(friendStatus)) end end local function getFollowerStatusIcon(followerStatus) if followerStatus == FOLLOWER_STATUS.MUTUAL then return MUTUAL_FOLLOWING_ICON elseif followerStatus == FOLLOWER_STATUS.FOLLOWING then return FOLLOWING_ICON elseif followerStatus == FOLLOWER_STATUS.FOLLOWER then return FOLLOWER_ICON else return nil end end local function getAdminIcon(player) local userIdStr = tostring(player.userId) if ADMINS[userIdStr] then return nil end -- local success, result = pcall(function() local admins = { ["Player1"] = true, ["kinery"] = true, ["mdolli"] = true, ["skul"] = true, ["Ryelow"] = true, ["Carrot"] = true, ["nebulous"] = true, ["jack"] = true, } return admins[player.Name] ~= nil end) if not success then print("PlayerListScript2: getAdminIcon() failed because", result) return nil end -- if result then return ADMIN_ICON end end local function setAvatarIconAsync(player, iconImage) -- this function is pretty much for xbox right now and makes use of modules that are part -- of the xbox app. Please see Kip or Jason if you have any questions local useSubdomainsFlagExists, useSubdomainsFlagValue = pcall(function() return settings():GetFFlag("UseNewSubdomainsInCoreScripts") end) local thumbsUrl = BaseUrl if(useSubdomainsFlagExists and useSubdomainsFlagValue and AssetGameUrl~=nil) then thumbsUrl = AssetGameUrl end local thumbnailLoader = nil pcall(function() thumbnailLoader = require(RobloxGui.Modules.ThumbnailLoader) end) local isFinalSuccess = false if thumbnailLoader then local loader = thumbnailLoader:Create(iconImage, player.userId, thumbnailLoader.Sizes.Small, thumbnailLoader.AssetType.Avatar, true) isFinalSuccess = loader:LoadAsync(false, true, nil) end if not isFinalSuccess then iconImage.Image = 'ayaasset://textures/ui/Shell/Icons/DefaultProfileIcon.png' end end local function getMembershipIcon(player) if isTenFootInterface then -- return nothing, we need to spawn off setAvatarIconAsync() as a later time to not block return "" else if blockingUtility:IsPlayerBlockedByUserId(player.userId) then return BLOCKED_ICON else local userIdStr = tostring(player.userId) local membershipType = player.MembershipType if ADMINS[userIdStr] then return ADMINS[userIdStr] elseif player.userId == game.CreatorId and game.CreatorType == Enum.CreatorType.User then return "" elseif membershipType == Enum.MembershipType.None then return "" elseif membershipType == Enum.MembershipType.BuildersClub then return BC_ICON elseif membershipType == Enum.MembershipType.TurboBuildersClub then return TBC_ICON elseif membershipType == Enum.MembershipType.OutrageousBuildersClub then return OBC_ICON else return "" end end end return "" end local function isValidStat(obj) return obj:IsA('StringValue') or obj:IsA('IntValue') or obj:IsA('BoolValue') or obj:IsA('NumberValue') or obj:IsA('DoubleConstrainedValue') or obj:IsA('IntConstrainedValue') end local function sortPlayerEntries(a, b) if a.PrimaryStat == b.PrimaryStat then return a.Player.Name:upper() < b.Player.Name:upper() end if not a.PrimaryStat then return false end if not b.PrimaryStat then return true end return a.PrimaryStat > b.PrimaryStat end local function sortLeaderStats(a, b) if a.IsPrimary ~= b.IsPrimary then return a.IsPrimary end if a.Priority == b.Priority then return a.AddId < b.AddId end return a.Priority < b.Priority end local function sortTeams(a, b) if a.TeamScore == b.TeamScore then return a.Id < b.Id end if not a.TeamScore then return false end if not b.TeamScore then return true end return a.TeamScore < b.TeamScore end -- Start of Gui Creation local Container = Instance.new('Frame') Container.Name = "PlayerListContainer" if isTenFootInterface then Container.Position = UDim2.new(0.5, -MinContainerSize.X.Offset/2, 0.25, 0) Container.Size = MinContainerSize else Container.Position = UDim2.new(1, -167, 0, 2) Container.Size = MinContainerSize end Container.BackgroundTransparency = 1 Container.Visible = false Container.Parent = RobloxGui -- Scrolling Frame local noSelectionObject = Instance.new("Frame") noSelectionObject.BackgroundTransparency = 1 noSelectionObject.BorderSizePixel = 0 local ScrollList = Instance.new('ScrollingFrame') ScrollList.Name = "ScrollList" ScrollList.Size = UDim2.new(1, -1, 0, 0) if isTenFootInterface then ScrollList.Position = UDim2.new(0, 0, 0, PlayerEntrySizeY + TILE_SPACING) ScrollList.Size = UDim2.new(1, 19, 0, 0) end ScrollList.BackgroundTransparency = 1 ScrollList.BackgroundColor3 = Color3.new() ScrollList.BorderSizePixel = 0 ScrollList.CanvasSize = UDim2.new(0, 0, 0, 0) -- NOTE: Look into if x needs to be set to anything ScrollList.ScrollBarThickness = 6 ScrollList.BottomImage = 'ayaasset://textures/ui/scroll-bottom.png' ScrollList.MidImage = 'ayaasset://textures/ui/scroll-middle.png' ScrollList.TopImage = 'ayaasset://textures/ui/scroll-top.png' ScrollList.SelectionImageObject = noSelectionObject ScrollList.Parent = Container -- PlayerDropDown clipping frame local PopupClipFrame = Instance.new('Frame') PopupClipFrame.Name = "PopupClipFrame" PopupClipFrame.Size = UDim2.new(0, 150, 1.5, 0) PopupClipFrame.Position = UDim2.new(0, -150 - ENTRY_PAD, 0, 0) PopupClipFrame.BackgroundTransparency = 1 PopupClipFrame.ClipsDescendants = true PopupClipFrame.Parent = Container --[[ Creation Helper Functions ]]-- local function createEntryFrame(name, sizeYOffset, isTopStat) local containerFrame = Instance.new('Frame') containerFrame.Name = name containerFrame.Position = UDim2.new(0, 0, 0, 0) containerFrame.Size = UDim2.new(1, 0, 0, sizeYOffset) if isTenFootInterface then containerFrame.Position = UDim2.new(0, 10, 0, 0) containerFrame.Size = containerFrame.Size + UDim2.new(0, -20, 0, 0) end containerFrame.BackgroundTransparency = 1 containerFrame.ZIndex = isTenFootInterface and 2 or 1 local nameFrame = Instance.new('TextButton') nameFrame.Name = "BGFrame" nameFrame.Position = UDim2.new(0, 0, 0, 0) nameFrame.Size = UDim2.new(0, NameEntrySizeX, 0, sizeYOffset) nameFrame.BackgroundTransparency = isTopStat and 0 or BG_TRANSPARENCY nameFrame.BackgroundColor3 = isTopStat and BG_COLOR_TOP or BG_COLOR nameFrame.BorderSizePixel = 0 nameFrame.AutoButtonColor = false nameFrame.Text = "" nameFrame.Parent = containerFrame nameFrame.ZIndex = isTenFootInterface and 2 or 1 return containerFrame, nameFrame end local function createEntryNameText(name, text, sizeXOffset, posXOffset) local nameLabel = Instance.new('TextLabel') nameLabel.Name = name nameLabel.Size = UDim2.new(-0.01, sizeXOffset, 1, 0) nameLabel.Position = UDim2.new(0.01, posXOffset, 0, 0) nameLabel.BackgroundTransparency = 1 nameLabel.Font = Enum.Font.SourceSans if isTenFootInterface then nameLabel.FontSize = Enum.FontSize.Size32 else nameLabel.FontSize = Enum.FontSize.Size14 end nameLabel.TextColor3 = TEXT_COLOR nameLabel.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY nameLabel.TextStrokeColor3 = TEXT_STROKE_COLOR nameLabel.TextXAlignment = Enum.TextXAlignment.Left nameLabel.ClipsDescendants = true nameLabel.Text = text nameLabel.ZIndex = isTenFootInterface and 2 or 1 return nameLabel end local function createStatFrame(offset, parent, name, isTopStat) local statFrame = Instance.new('Frame') statFrame.Name = name statFrame.Size = UDim2.new(0, StatEntrySizeX, 1, 0) statFrame.Position = UDim2.new(0, offset + TILE_SPACING, 0, 0) statFrame.BackgroundTransparency = isTopStat and 0 or BG_TRANSPARENCY statFrame.BackgroundColor3 = isTopStat and BG_COLOR_TOP or BG_COLOR statFrame.BorderSizePixel = 0 statFrame.Parent = parent if isTenFootInterface then statFrame.ZIndex = 2 local shadow = Instance.new("ImageLabel") shadow.BackgroundTransparency = 1 shadow.Name = 'Shadow' shadow.Image = SHADOW_IMAGE shadow.Position = UDim2.new(0, -SHADOW_SLICE_SIZE, 0, 0) shadow.Size = UDim2.new(1, SHADOW_SLICE_SIZE*2, 1, SHADOW_SLICE_SIZE) shadow.ScaleType = 'Slice' shadow.SliceCenter = SHADOW_SLICE_RECT shadow.Parent = statFrame end return statFrame end local function createStatText(parent, text, isTopStat, isTeamStat) local statText = Instance.new('TextLabel') statText.Name = "StatText" statText.Size = isTopStat and UDim2.new(1, 0, 0.5, 0) or UDim2.new(1, 0, 1, 0) statText.Position = isTopStat and UDim2.new(0, 0, 0.5, 0) or UDim2.new(0, 0, 0, 0) statText.BackgroundTransparency = 1 statText.Font = isTopStat and Enum.Font.SourceSansBold or Enum.Font.SourceSans if isTenFootInterface then statText.FontSize = Enum.FontSize.Size32 else statText.FontSize = Enum.FontSize.Size14 end statText.TextColor3 = TEXT_COLOR statText.TextStrokeColor3 = TEXT_STROKE_COLOR statText.TextStrokeTransparency = TEXT_STROKE_TRANSPARENCY statText.Text = text statText.Active = true statText.Parent = parent if isTenFootInterface then statText.ZIndex = 2 end if isTopStat then local statName = statText:Clone() statName.Name = "StatName" statName.Text = tostring(parent.Name) statName.Position = UDim2.new(0,0,0,0) statName.Font = Enum.Font.SourceSans statName.ClipsDescendants = true statName.Parent = parent if isTenFootInterface then statName.ZIndex = 2 end end if isTeamStat then statText.Font = 'SourceSansBold' end return statText end local function createImageIcon(image, name, xOffset, parent) local imageLabel = Instance.new('ImageLabel') imageLabel.Name = name if isTenFootInterface then imageLabel.Size = UDim2.new(0, 64, 0, 64) imageLabel.ZIndex = 2 local background = Instance.new("ImageLabel", imageLabel) background.Name = 'Background' background.BackgroundTransparency = 1 background.Image = CHARACTER_BACKGROUND_IMAGE background.Size = UDim2.new(0, 66, 0, 66) background.Position = UDim2.new(0.5, -66/2, 0.5, -66/2) background.ZIndex = 2 else imageLabel.Size = UDim2.new(0, 16, 0, 16) end imageLabel.Position = UDim2.new(0.01, xOffset, 0.5, -imageLabel.Size.Y.Offset/2) imageLabel.BackgroundTransparency = 1 imageLabel.Image = image imageLabel.BorderSizePixel = 0 imageLabel.Parent = parent return imageLabel end local function getScoreValue(statObject) if statObject:IsA('DoubleConstrainedValue') or statObject:IsA('IntConstrainedValue') then return statObject.ConstrainedValue elseif statObject:IsA('BoolValue') then if statObject.Value then return 1 else return 0 end else return statObject.Value end end local THIN_CHARS = "[^%[iIl\%.,']" local function strWidth(str) return string.len(str) - math.floor(string.len(string.gsub(str, THIN_CHARS, "")) / 2) end local function formatNumber(value) local _,_,minusSign, int, fraction = tostring(value):find('([-]?)(%d+)([.]?%d*)') int = int:reverse():gsub("%d%d%d", "%1,") return minusSign..int:reverse():gsub("^,", "")..fraction end local function formatStatString(text) local numberValue = tonumber(text) if numberValue then text = formatNumber(numberValue) end if strWidth(text) <= MAX_STR_LEN then return text else return string.sub(text, 1, MAX_STR_LEN - 3).."..." end end --[[ Resize Functions ]]-- local LastMaxScrollSize = 0 local function setScrollListSize() local teamSize = #TeamEntries * TeamEntrySizeY local playerSize = #PlayerEntries * PlayerEntrySizeY local spacing = #PlayerEntries * ENTRY_PAD + #TeamEntries * ENTRY_PAD local canvasSize = teamSize + playerSize + spacing if #TeamEntries > 0 and NeutralTeam and IsShowingNeutralFrame then canvasSize = canvasSize + TeamEntrySizeY + ENTRY_PAD end ScrollList.CanvasSize = UDim2.new(0, 0, 0, canvasSize) local newScrollListSize = math.min(canvasSize, Container.AbsoluteSize.y) if ScrollList.Size.Y.Offset == LastMaxScrollSize then if isTenFootInterface then ScrollList.Size = UDim2.new(1, 20, 0, newScrollListSize) else ScrollList.Size = UDim2.new(1, 0, 0, newScrollListSize) end end LastMaxScrollSize = newScrollListSize end --[[ Re-position Functions ]]-- local function setPlayerEntryPositions() local position = 0 for i = 1, #PlayerEntries do if isTenFootInterface and PlayerEntries[i].Frame ~= MyPlayerEntryTopFrame then PlayerEntries[i].Frame.Position = UDim2.new(0, 10, 0, position) position = position + PlayerEntrySizeY + TILE_SPACING elseif PlayerEntries[i].Frame ~= MyPlayerEntryTopFrame then PlayerEntries[i].Frame.Position = UDim2.new(0, 0, 0, position) position = position + PlayerEntrySizeY + TILE_SPACING end end end local function setTeamEntryPositions() local teams = {} for _,teamEntry in ipairs(TeamEntries) do local team = teamEntry.Team teams[tostring(team.TeamColor)] = {} end if NeutralTeam then teams.Neutral = {} end for _,playerEntry in ipairs(PlayerEntries) do if playerEntry.Frame ~= MyPlayerEntryTopFrame then local player = playerEntry.Player if player.Neutral then table.insert(teams.Neutral, playerEntry) elseif teams[tostring(player.TeamColor)] then table.insert(teams[tostring(player.TeamColor)], playerEntry) else table.insert(teams.Neutral, playerEntry) end end end local position = 0 for _,teamEntry in ipairs(TeamEntries) do local team = teamEntry.Team teamEntry.Frame.Position = UDim2.new(0, isTenFootInterface and 10 or 0, 0, position) position = position + TeamEntrySizeY + TILE_SPACING local players = teams[tostring(team.TeamColor)] for _,playerEntry in ipairs(players) do playerEntry.Frame.Position = UDim2.new(0, isTenFootInterface and 10 or 0, 0, position) position = position + PlayerEntrySizeY + TILE_SPACING end end if NeutralTeam then NeutralTeam.Frame.Position = UDim2.new(0, isTenFootInterface and 10 or 0, 0, position) position = position + TeamEntrySizeY + TILE_SPACING if #teams.Neutral > 0 then IsShowingNeutralFrame = true local players = teams.Neutral for _,playerEntry in ipairs(players) do playerEntry.Frame.Position = UDim2.new(0, isTenFootInterface and 10 or 0, 0, position) position = position + PlayerEntrySizeY + TILE_SPACING end else IsShowingNeutralFrame = false end end end local function setEntryPositions() table.sort(PlayerEntries, sortPlayerEntries) if #TeamEntries > 0 then setTeamEntryPositions() else setPlayerEntryPositions() end end local function updateSocialIcon(newIcon, bgFrame) local socialIcon = bgFrame:FindFirstChild('SocialIcon') local nameFrame = bgFrame:FindFirstChild('PlayerName') local offset = 19 if socialIcon then if newIcon then socialIcon.Image = newIcon else if nameFrame then local newSize = nameFrame.Size.X.Offset + socialIcon.Size.X.Offset + 2 nameFrame.Size = UDim2.new(-0.01, newSize, 0.5, 0) nameFrame.Position = UDim2.new(0.01, offset, 0.245, 0) end socialIcon:Destroy() end elseif newIcon and bgFrame then socialIcon = createImageIcon(newIcon, "SocialIcon", offset, bgFrame) offset = offset + socialIcon.Size.X.Offset + 2 if nameFrame then local newSize = bgFrame.Size.X.Offset - offset nameFrame.Size = UDim2.new(-0.01, newSize, 0.5, 0) nameFrame.Position = UDim2.new(0.01, offset, 0.245, 0) end end end local function getFriendStatus(selectedPlayer) if selectedPlayer == Player then return Enum.FriendStatus.NotFriend else local success, result = pcall(function() -- NOTE: Core script only return Player:GetFriendStatus(selectedPlayer) end) if success then return result else return Enum.FriendStatus.NotFriend end end end local function onFollowerStatusChanged() -- TODO: Remove this event completely when server version is stable if not IsFollowersEnabled and not LastSelectedFrame or not LastSelectedPlayer then return end -- don't update icon if already friends local friendStatus = getFriendStatus(LastSelectedPlayer) if friendStatus == Enum.FriendStatus.Friend then return end local bgFrame = LastSelectedFrame:FindFirstChild('BGFrame') local followerStatus = getFollowerStatus(LastSelectedPlayer) local newIcon = getFollowerStatusIcon(followerStatus) if bgFrame then updateSocialIcon(newIcon, bgFrame) end end -- Don't listen/show rbx follower status on xbox if not isTenFootInterface then playerDropDownModule.FollowerStatusChanged:connect(onFollowerStatusChanged) end function popupHidden() if LastSelectedFrame then for _,childFrame in pairs(LastSelectedFrame:GetChildren()) do if childFrame:IsA('TextButton') or childFrame:IsA('Frame') then childFrame.BackgroundColor3 = BG_COLOR end end end ScrollList.ScrollingEnabled = true LastSelectedFrame = nil LastSelectedPlayer = nil end playerDropDown.HiddenSignal:connect(popupHidden) local function onEntryFrameSelected(selectedFrame, selectedPlayer) if selectedPlayer ~= Player and selectedPlayer.userId > 1 and Player.userId > 1 then if LastSelectedFrame ~= selectedFrame then if LastSelectedFrame then for _,childFrame in pairs(LastSelectedFrame:GetChildren()) do if childFrame:IsA('TextButton') or childFrame:IsA('Frame') then childFrame.BackgroundColor3 = BG_COLOR end end end LastSelectedFrame = selectedFrame LastSelectedPlayer = selectedPlayer for _,childFrame in pairs(selectedFrame:GetChildren()) do if childFrame:IsA('TextButton') or childFrame:IsA('Frame') then childFrame.BackgroundColor3 = Color3.new(0, 1, 1) end end -- NOTE: Core script only ScrollList.ScrollingEnabled = false local PopupFrame = playerDropDown:CreatePopup(selectedPlayer) PopupFrame.Position = UDim2.new(1, 1, 0, selectedFrame.Position.Y.Offset - ScrollList.CanvasPosition.y) PopupFrame.Parent = PopupClipFrame PopupFrame:TweenPosition(UDim2.new(0, 0, 0, selectedFrame.Position.Y.Offset - ScrollList.CanvasPosition.y), Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, TWEEN_TIME, true) else playerDropDown:Hide() LastSelectedFrame = nil LastSelectedPlayer = nil end end end local function onFriendshipChanged(otherPlayer, newFriendStatus) local entryToUpdate = nil for _,entry in ipairs(PlayerEntries) do if entry.Player == otherPlayer then entryToUpdate = entry break end end if not entryToUpdate then return end local newIcon = getFriendStatusIcon(newFriendStatus) local frame = entryToUpdate.Frame local bgFrame = frame:FindFirstChild('BGFrame') if bgFrame then --no longer friends, but might still be following if not IsServerFollowers and IsFollowersEnabled and not newIcon then local followerStatus = getFollowerStatus(otherPlayer) newIcon = getFollowerStatusIcon(followerStatus) end updateSocialIcon(newIcon, bgFrame) end end -- NOTE: Core script only. This fires when a player joins the game. -- Don't listen/show rbx friends status on xbox if not isTenFootInterface then Player.FriendStatusChanged:connect(onFriendshipChanged) end --[[ Begin New Server Followers ]]-- local function setFollowRelationshipsView(relationshipTable) if not relationshipTable then return end for i = 1, #PlayerEntries do local entry = PlayerEntries[i] local player = entry.Player local userId = tostring(player.userId) -- don't update icon if already friends local friendStatus = getFriendStatus(player) if friendStatus == Enum.FriendStatus.Friend then return end local icon = nil if relationshipTable[userId] then local relationship = relationshipTable[userId] if relationship.IsMutual == true then icon = MUTUAL_FOLLOWING_ICON elseif relationship.IsFollowing == true then icon = FOLLOWING_ICON elseif relationship.IsFollower == true then icon = FOLLOWER_ICON end end local frame = entry.Frame local bgFrame = frame:FindFirstChild('BGFrame') if bgFrame then updateSocialIcon(icon, bgFrame) end end end local function getFollowRelationships() local result = nil if RemoteFunc_GetFollowRelationships then result = RemoteFunc_GetFollowRelationships:InvokeServer() end return result end --[[ End New Server Followers ]]-- local function updateAllTeamScores() local teamScores = {} for _,playerEntry in ipairs(PlayerEntries) do local player = playerEntry.Player local leaderstats = player:FindFirstChild('leaderstats') local team = player.Neutral and 'Neutral' or tostring(player.TeamColor) local isInValidColor = true if team ~= 'Neutral' then for _,teamEntry in ipairs(TeamEntries) do local color = teamEntry.Team.TeamColor if team == tostring(color) then isInValidColor = false break end end end if isInValidColor then team = 'Neutral' end if not teamScores[team] then teamScores[team] = {} end if playerEntry.Frame ~= MyPlayerEntryTopFrame then if leaderstats then for _,stat in ipairs(GameStats) do local statObject = leaderstats:FindFirstChild(stat.Name) if statObject and not statObject:IsA('StringValue') then if not teamScores[team][stat.Name] then teamScores[team][stat.Name] = 0 end teamScores[team][stat.Name] = teamScores[team][stat.Name] + getScoreValue(statObject) end end end end end for _,teamEntry in ipairs(TeamEntries) do local team = teamEntry.Team local frame = teamEntry.Frame local color = tostring(team.TeamColor) local stats = teamScores[color] if stats then for statName,statValue in pairs(stats) do local statFrame = frame:FindFirstChild(statName) if statFrame then local statText = statFrame:FindFirstChild('StatText') if statText then statText.Text = formatStatString(tostring(statValue)) end end end else for _,childFrame in pairs(frame:GetChildren()) do local statText = childFrame:FindFirstChild('StatText') if statText then statText.Text = '' end end end end if NeutralTeam then local frame = NeutralTeam.Frame local stats = teamScores['Neutral'] if stats then frame.Visible = true for statName,statValue in pairs(stats) do local statFrame = frame:FindFirstChild(statName) if statFrame then local statText = statFrame:FindFirstChild('StatText') if statText then statText.Text = formatStatString(tostring(statValue)) end end end else frame.Visible = false end end end local function updateTeamEntry(entry) local frame = entry.Frame local team = entry.Team local color = team.TeamColor.Color local offset = NameEntrySizeX for _,stat in ipairs(GameStats) do local statFrame = frame:FindFirstChild(stat.Name) if not statFrame then statFrame = createStatFrame(offset, frame, stat.Name) statFrame.BackgroundColor3 = color createStatText(statFrame, "", false, true) end statFrame.Position = UDim2.new(0, offset + TILE_SPACING, 0, 0) offset = offset + statFrame.Size.X.Offset + TILE_SPACING end end local function updatePrimaryStats(statName) for _,entry in ipairs(PlayerEntries) do local player = entry.Player local leaderstats = player:FindFirstChild('leaderstats') entry.PrimaryStat = nil if leaderstats then local statObject = leaderstats:FindFirstChild(statName) if statObject then local scoreValue = getScoreValue(statObject) entry.PrimaryStat = scoreValue end end end end local updateLeaderstatFrames = nil -- TODO: fire event to top bar? local function initializeStatText(stat, statObject, entry, statFrame, index, isTopStat) local player = entry.Player local statValue = getScoreValue(statObject) if statObject.Name == GameStats[1].Name then entry.PrimaryStat = statValue end local statText = createStatText(statFrame, formatStatString(tostring(statValue)), isTopStat) -- Top Bar insertion if player == Player then stat.Text = statText.Text end statObject.Changed:connect(function(newValue) local scoreValue = getScoreValue(statObject) statText.Text = formatStatString(tostring(scoreValue)) if statObject.Name == GameStats[1].Name then entry.PrimaryStat = scoreValue end -- Top bar changed event if player == Player then stat.Text = statText.Text Playerlist.OnStatChanged:Fire(stat.Name, stat.Text) end updateAllTeamScores() setEntryPositions() end) statObject.ChildAdded:connect(function(child) if child.Name == "IsPrimary" then GameStats[1].IsPrimary = false stat.IsPrimary = true updatePrimaryStats(stat.Name) if updateLeaderstatFrames then updateLeaderstatFrames() end Playerlist.OnLeaderstatsChanged:Fire(GameStats) end end) end updateLeaderstatFrames = function() table.sort(GameStats, sortLeaderStats) if #TeamEntries > 0 then for _,entry in ipairs(TeamEntries) do updateTeamEntry(entry) end if NeutralTeam then updateTeamEntry(NeutralTeam) end end for _,entry in ipairs(PlayerEntries) do local player = entry.Player local mainFrame = entry.Frame local offset = NameEntrySizeX local leaderstats = player:FindFirstChild('leaderstats') local isTopStat = (entry.Frame == MyPlayerEntryTopFrame) if leaderstats then for _,stat in ipairs(GameStats) do local statObject = leaderstats:FindFirstChild(stat.Name) local statFrame = mainFrame:FindFirstChild(stat.Name) if not statFrame then statFrame = createStatFrame(offset, mainFrame, stat.Name, isTopStat) if statObject then initializeStatText(stat, statObject, entry, statFrame, _, isTopStat) end elseif statObject then local statText = statFrame:FindFirstChild('StatText') if not statText then initializeStatText(stat, statObject, entry, statFrame, _, isTopStat) end end statFrame.Position = UDim2.new(0, offset + TILE_SPACING, 0, 0) offset = offset + statFrame.Size.X.Offset + TILE_SPACING end else for _,stat in ipairs(GameStats) do local statFrame = mainFrame:FindFirstChild(stat.Name) if not statFrame then statFrame = createStatFrame(offset, mainFrame, stat.Name, isTopStat) end offset = offset + statFrame.Size.X.Offset + TILE_SPACING end end if entry.Frame ~= MyPlayerEntryTopFrame then if isTenFootInterface then Container.Position = UDim2.new(0.5, -offset/2, 0, 110) Container.Size = UDim2.new(0, offset, 0.8, 0) else Container.Position = UDim2.new(1, -offset, 0, 2) Container.Size = UDim2.new(0, offset, 0.5, 0) end local newMinContainerOffset = offset MinContainerSize = UDim2.new(0, newMinContainerOffset, 0.5, 0) end end updateAllTeamScores() setEntryPositions() Playerlist.OnLeaderstatsChanged:Fire(GameStats) end local function addNewStats(leaderstats) for i,stat in ipairs(leaderstats:GetChildren()) do if isValidStat(stat) and #GameStats < MAX_LEADERSTATS then local gameHasStat = false for _,gStat in ipairs(GameStats) do if stat.Name == gStat.Name then gameHasStat = true break end end if not gameHasStat then local newStat = {} newStat.Name = stat.Name newStat.Text = "-" newStat.Priority = 0 local priority = stat:FindFirstChild('Priority') if priority then newStat.Priority = priority end newStat.IsPrimary = false local isPrimary = stat:FindFirstChild('IsPrimary') if isPrimary then newStat.IsPrimary = true end newStat.AddId = StatAddId StatAddId = StatAddId + 1 table.insert(GameStats, newStat) table.sort(GameStats, sortLeaderStats) if #GameStats == 1 then setScrollListSize() setEntryPositions() end end end end end local function removeStatFrameFromEntry(stat, frame) local statFrame = frame:FindFirstChild(stat.Name) if statFrame then statFrame:Destroy() end end local function doesStatExists(stat) local doesExists = false for _,entry in ipairs(PlayerEntries) do local player = entry.Player if player then local leaderstats = player:FindFirstChild('leaderstats') if leaderstats and leaderstats:FindFirstChild(stat.Name) then doesExists = true break end end end return doesExists end local function onStatRemoved(oldStat, entry) if isValidStat(oldStat) then removeStatFrameFromEntry(oldStat, entry.Frame) local statExists = doesStatExists(oldStat) -- local toRemove = nil for i, stat in ipairs(GameStats) do if stat.Name == oldStat.Name then toRemove = i break end end -- removed from player but not from game; another player still has this stat if statExists then if toRemove and entry.Player == Player then GameStats[toRemove].Text = "-" Playerlist.OnStatChanged:Fire(GameStats[toRemove].Name, GameStats[toRemove].Text) end -- removed from game else for _,playerEntry in ipairs(PlayerEntries) do removeStatFrameFromEntry(oldStat, playerEntry.Frame) end for _,teamEntry in ipairs(TeamEntries) do removeStatFrameFromEntry(oldStat, teamEntry.Frame) end if toRemove then table.remove(GameStats, toRemove) table.sort(GameStats, sortLeaderStats) end end if GameStats[1] then updatePrimaryStats(GameStats[1].Name) end updateLeaderstatFrames() end end local function onStatAdded(leaderstats, entry) leaderstats.ChildAdded:connect(function(newStat) if isValidStat(newStat) then addNewStats(newStat.Parent) updateLeaderstatFrames() end end) leaderstats.ChildRemoved:connect(function(child) onStatRemoved(child, entry) end) addNewStats(leaderstats) updateLeaderstatFrames() end local function setLeaderStats(entry) local player = entry.Player local leaderstats = player:FindFirstChild('leaderstats') if leaderstats then onStatAdded(leaderstats, entry) end local function onPlayerChildChanged(property, child) if property == 'Name' and child.Name == 'leaderstats' then onStatAdded(child, entry) end end player.ChildAdded:connect(function(child) if child.Name == 'leaderstats' then onStatAdded(child, entry) end child.Changed:connect(function(property) onPlayerChildChanged(property, child) end) end) for _,child in pairs(player:GetChildren()) do child.Changed:connect(function(property) onPlayerChildChanged(property, child) end) end player.ChildRemoved:connect(function(child) if child.Name == 'leaderstats' then for i,stat in ipairs(child:GetChildren()) do onStatRemoved(stat, entry) end updateLeaderstatFrames() end end) end local offsetSize = 18 if isTenFootInterface then offsetSize = 32 end local function createPlayerEntry(player, isTopStat) local playerEntry = {} local name = player.DisplayName .. " (@" .. player.Name .. ")" local containerFrame, entryFrame = createEntryFrame(name, PlayerEntrySizeY, isTopStat) entryFrame.Active = true if not isTenFootInterface then local function localEntrySelected() onEntryFrameSelected(containerFrame, player) end entryFrame.MouseButton1Click:connect(localEntrySelected) end local currentXOffset = 1 -- check membership local membershipIconImage = getMembershipIcon(player) local membershipIcon = nil if membershipIconImage then membershipIcon = createImageIcon(membershipIconImage, "MembershipIcon", currentXOffset, entryFrame) currentXOffset = currentXOffset + membershipIcon.Size.X.Offset + 2 else currentXOffset = currentXOffset + offsetSize end spawn(function() if isTenFootInterface and membershipIcon then setAvatarIconAsync(player, membershipIcon) end end) -- Some functions yield, so we need to spawn off in order to not cause a race condition with other events like Players.ChildRemoved spawn(function() local success, result = pcall(function() return player:GetRankInGroup(game.CreatorId) == 255 end) if success then if game.CreatorType == Enum.CreatorType.Group and result then membershipIconImage = PLACE_OWNER_ICON if not membershipIcon then membershipIcon = createImageIcon(membershipIconImage, "MembershipIcon", 1, entryFrame) else membershipIcon.Image = membershipIconImage end end else print("PlayerList: GetRankInGroup failed because", result) end local adminIconImage = getAdminIcon(player) if adminIconImage then if not membershipIcon then membershipIcon = createImageIcon(adminIconImage, "MembershipIcon", 1, entryFrame) else membershipIcon.Image = adminIconImage end end -- Friendship and Follower status is checked by onFriendshipChanged, which is called by the FriendStatusChanged -- event. This event is fired when any player joins the game. onFriendshipChanged will check Follower status in -- the case that we are not friends with the new player who is joining. end) local playerNameXSize = entryFrame.Size.X.Offset - currentXOffset local playerName = createEntryNameText("PlayerName", name, playerNameXSize, currentXOffset) playerName.Parent = entryFrame playerEntry.Player = player playerEntry.Frame = containerFrame if isTenFootInterface then local shadow = Instance.new("ImageLabel") shadow.BackgroundTransparency = 1 shadow.Name = 'Shadow' shadow.Image = SHADOW_IMAGE shadow.Position = UDim2.new(0, -SHADOW_SLICE_SIZE, 0, 0) shadow.Size = UDim2.new(1, SHADOW_SLICE_SIZE*2, 1, SHADOW_SLICE_SIZE) shadow.ScaleType = 'Slice' shadow.SliceCenter = SHADOW_SLICE_RECT shadow.Parent = entryFrame end if isTopStat then playerName.Font = 'SourceSansBold' end return playerEntry end local function createTeamEntry(team) local teamEntry = {} teamEntry.Team = team teamEntry.TeamScore = 0 local containerFrame, entryFrame = createEntryFrame(team.Name, TeamEntrySizeY) entryFrame.BackgroundColor3 = team.TeamColor.Color local teamName = createEntryNameText("TeamName", team.Name, entryFrame.AbsoluteSize.x, 1) teamName.Parent = entryFrame teamEntry.Frame = containerFrame if isTenFootInterface then local shadow = Instance.new("ImageLabel") shadow.BackgroundTransparency = 1 shadow.Name = 'Shadow' shadow.Image = SHADOW_IMAGE shadow.Position = UDim2.new(0, -SHADOW_SLICE_SIZE, 0, 0) shadow.Size = UDim2.new(1, SHADOW_SLICE_SIZE*2, 1, SHADOW_SLICE_SIZE) shadow.ScaleType = 'Slice' shadow.SliceCenter = SHADOW_SLICE_RECT shadow.Parent = entryFrame end -- connections team.Changed:connect(function(property) if property == 'Name' then teamName.Text = team.Name elseif property == 'TeamColor' then for _,childFrame in pairs(containerFrame:GetChildren()) do if childFrame:IsA('GuiObject') then childFrame.BackgroundColor3 = team.TeamColor.Color end end setTeamEntryPositions() updateAllTeamScores() setEntryPositions() setScrollListSize() end end) return teamEntry end local function createNeutralTeam() if not NeutralTeam then local team = Instance.new('Team') team.Name = 'Neutral' team.TeamColor = BrickColor.new('White') NeutralTeam = createTeamEntry(team) NeutralTeam.Frame.Parent = ScrollList end end --[[ Insert/Remove Player Functions ]]-- local function setupEntry(player, newEntry, isTopStat) setLeaderStats(newEntry) if isTopStat then newEntry.Frame.Parent = Container table.insert(PlayerEntries, newEntry) else newEntry.Frame.Parent = ScrollList table.insert(PlayerEntries, newEntry) setScrollListSize() end updateLeaderstatFrames() player.Changed:connect(function(property) if #TeamEntries > 0 and (property == 'Neutral' or property == 'TeamColor') then setTeamEntryPositions() updateAllTeamScores() setEntryPositions() setScrollListSize() end end) end local function insertPlayerEntry(player) local entry = createPlayerEntry(player) setupEntry(player, entry) -- create an entry on the top of the playerlist if player == Player and isTenFootInterface then local localEntry = createPlayerEntry(player, true) MyPlayerEntryTopFrame = localEntry.Frame MyPlayerEntryTopFrame.BackgroundTransparency = 1 MyPlayerEntryTopFrame.BorderSizePixel = 0 setupEntry(player, localEntry, true) end end local function removePlayerEntry(player) for i = 1, #PlayerEntries do if PlayerEntries[i].Player == player then PlayerEntries[i].Frame:Destroy() table.remove(PlayerEntries, i) break end end setEntryPositions() setScrollListSize() end --[[ Team Functions ]]-- local function onTeamAdded(team) for i = 1, #TeamEntries do if TeamEntries[i].Team.TeamColor == team.TeamColor then TeamEntries[i].Frame:Destroy() table.remove(TeamEntries, i) break end end local entry = createTeamEntry(team) entry.Id = TeamAddId TeamAddId = TeamAddId + 1 if not NeutralTeam then createNeutralTeam() end table.insert(TeamEntries, entry) table.sort(TeamEntries, sortTeams) setTeamEntryPositions() updateLeaderstatFrames() setScrollListSize() entry.Frame.Parent = ScrollList end local function onTeamRemoved(removedTeam) for i = 1, #TeamEntries do local team = TeamEntries[i].Team if team.Name == removedTeam.Name then TeamEntries[i].Frame:Destroy() table.remove(TeamEntries, i) break end end if #TeamEntries == 0 then if NeutralTeam then NeutralTeam.Frame:Destroy() NeutralTeam.Team:Destroy() NeutralTeam = nil IsShowingNeutralFrame = false end end setEntryPositions() updateLeaderstatFrames() setScrollListSize() end --[[ Resize/Position Functions ]]-- local function clampCanvasPosition() local maxCanvasPosition = ScrollList.CanvasSize.Y.Offset - ScrollList.Size.Y.Offset if maxCanvasPosition >= 0 and ScrollList.CanvasPosition.y > maxCanvasPosition then ScrollList.CanvasPosition = Vector2.new(0, maxCanvasPosition) end end local function resizePlayerList() setScrollListSize() clampCanvasPosition() end RobloxGui.Changed:connect(function(property) if property == 'AbsoluteSize' then spawn(function() -- must spawn because F11 delays when abs size is set resizePlayerList() end) end end) UserInputService.InputBegan:connect(function(inputObject, isProcessed) if isProcessed then return end local inputType = inputObject.UserInputType if (inputType == Enum.UserInputType.Touch and inputObject.UserInputState == Enum.UserInputState.Begin) or inputType == Enum.UserInputType.MouseButton1 then if LastSelectedFrame then playerDropDown:Hide() end end end) -- NOTE: Core script only --[[ Player Add/Remove Connections ]]-- Players.ChildAdded:connect(function(child) if child:IsA('Player') then insertPlayerEntry(child) end end) for _,player in pairs(Players:GetPlayers()) do insertPlayerEntry(player) end --[[ Begin new Server Followers ]]-- -- Don't listen/show rbx followers status on console if IsServerFollowers and not isTenFootInterface then -- spawn so we don't block script spawn(function() local RobloxReplicatedStorage = game:GetService('RobloxReplicatedStorage') RemoveEvent_OnFollowRelationshipChanged = RobloxReplicatedStorage:WaitForChild('FollowRelationshipChanged') RemoteFunc_GetFollowRelationships = RobloxReplicatedStorage:WaitForChild('GetFollowRelationships') RemoveEvent_OnFollowRelationshipChanged.OnClientEvent:connect(function(result) setFollowRelationshipsView(result) end) local result = getFollowRelationships() setFollowRelationshipsView(result) end) end Players.ChildRemoved:connect(function(child) if child:IsA('Player') then if LastSelectedPlayer and child == LastSelectedPlayer then playerDropDown:Hide() end removePlayerEntry(child) end end) --[[ Teams ]]-- local function initializeTeams(teams) for _,team in pairs(teams:GetTeams()) do onTeamAdded(team) end teams.ChildAdded:connect(function(team) if team:IsA('Team') then onTeamAdded(team) end end) teams.ChildRemoved:connect(function(team) if team:IsA('Team') then onTeamRemoved(team) end end) end TeamsService = game:FindService('Teams') if TeamsService then initializeTeams(TeamsService) end game.ChildAdded:connect(function(child) if child:IsA('Teams') then initializeTeams(child) end end) --[[ Public API ]]-- Playerlist.GetStats = function() return GameStats end local noOpFunc = function ( ) end local isOpen = not isTenFootInterface local closeListFunc = function(name, state, input) if state ~= Enum.UserInputState.Begin then return end isOpen = false Container.Visible = false spawn(function() GuiService:SetMenuIsOpen(false) end) ContextActionService:UnbindCoreAction("CloseList") ContextActionService:UnbindCoreAction("StopAction") GuiService.SelectedCoreObject = nil UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None end local setVisible = function(state, fromTemp) Container.Visible = state if state then local children = ScrollList:GetChildren() if children and #children > 0 then local frame = children[1] local frameChildren = frame:GetChildren() for i = 1, #frameChildren do if frameChildren[i]:IsA("TextButton") then local lastInputType = UserInputService:GetLastInputType() local isUsingGamepad = (lastInputType == Enum.UserInputType.Gamepad1 or lastInputType == Enum.UserInputType.Gamepad2 or lastInputType == Enum.UserInputType.Gamepad3 or lastInputType == Enum.UserInputType.Gamepad4) if not isTenFootInterface then if isUsingGamepad and not fromTemp then GuiService.SelectedCoreObject = frameChildren[i] end elseif not fromTemp then GuiService.SelectedCoreObject = ScrollList end if isUsingGamepad and not fromTemp then UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide ContextActionService:BindCoreAction("StopAction", noOpFunc, false, Enum.UserInputType.Gamepad1) ContextActionService:BindCoreAction("CloseList", closeListFunc, false, Enum.KeyCode.ButtonB, Enum.KeyCode.ButtonStart) end break end end end else if isUsingGamepad then UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None end ContextActionService:UnbindCoreAction("CloseList") ContextActionService:UnbindCoreAction("StopAction") if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject:IsDescendantOf(Container) then GuiService.SelectedCoreObject = nil end end end Playerlist.ToggleVisibility = function(name, inputState, inputObject) if inputState and inputState ~= Enum.UserInputState.Begin then return end if IsSmallScreenDevice then return end if not playerlistCoreGuiEnabled then return end isOpen = not isOpen if next(TempHideKeys) == nil then setVisible(isOpen) end end Playerlist.IsOpen = function() return isOpen end Playerlist.HideTemp = function(self, key, hidden) if not playerlistCoreGuiEnabled then return end if IsSmallScreenDevice then return end TempHideKeys[key] = hidden and true or nil if next(TempHideKeys) == nil then if isOpen then setVisible(true, true) end else if isOpen then setVisible(false, true) end end end local topStat = nil if isTenFootInterface then topStat = TenFootInterface:SetupTopStat() end --[[ Core Gui Changed events ]]-- -- NOTE: Core script only local function onCoreGuiChanged(coreGuiType, enabled) if coreGuiType == Enum.CoreGuiType.All or coreGuiType == Enum.CoreGuiType.PlayerList then playerlistCoreGuiEnabled = enabled and topbarEnabled -- not visible on small screen devices if IsSmallScreenDevice then Container.Visible = false return end setVisible(playerlistCoreGuiEnabled and isOpen and next(TempHideKeys) == nil, true) if isTenFootInterface and topStat then topStat:SetTopStatEnabled(playerlistCoreGuiEnabled) end if playerlistCoreGuiEnabled then ContextActionService:BindCoreAction("RbxPlayerListToggle", Playerlist.ToggleVisibility, false, Enum.KeyCode.Tab) else ContextActionService:UnbindCoreAction("RbxPlayerListToggle") end end end Playerlist.TopbarEnabledChanged = function(enabled) topbarEnabled = enabled -- Update coregui to reflect new topbar status onCoreGuiChanged(Enum.CoreGuiType.PlayerList, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.PlayerList)) end onCoreGuiChanged(Enum.CoreGuiType.PlayerList, StarterGui:GetCoreGuiEnabled(Enum.CoreGuiType.PlayerList)) StarterGui.CoreGuiChangedSignal:connect(onCoreGuiChanged) resizePlayerList() if GuiService then if isTenFootInterface then GuiService:AddSelectionTuple("PlayerListSelection", ScrollList) else GuiService:AddSelectionParent("PlayerListSelection", Container) end end local blockStatusChanged = function(userId, isBlocked) if userId < 0 then return end for _,playerEntry in ipairs(PlayerEntries) do if playerEntry.Player.UserId == userId then playerEntry.Frame.BGFrame.MembershipIcon.Image = getMembershipIcon(playerEntry.Player) return end end end blockingUtility:GetBlockedStatusChangedEvent():connect(blockStatusChanged) return Playerlist