forked from aya/aya
1765 lines
59 KiB
Lua
1765 lines
59 KiB
Lua
--[[
|
|
// 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
|