This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
Binary file not shown.
@@ -0,0 +1,338 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
-- App's Main
local CoreGui = Game:GetService("CoreGui")
local ContentProvider = Game:GetService("ContentProvider")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
-- TODO: Will use for re-auth when finished
local UserInputService = game:GetService('UserInputService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local AppHubModule = require(Modules:FindFirstChild('AppHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GameGenreModule = require(Modules:FindFirstChild('GameGenre'))
local GameDetailModule = require(Modules:FindFirstChild('GameDetail'))
local OverscanScreenModule = require(Modules:FindFirstChild('OverscanScreen'))
local EngagementScreenModule = require(Modules:FindFirstChild('EngagementScreen'))
local BadgeScreenModule = require(Modules:FindFirstChild('BadgeScreen'))
local CameraManager = require(Modules:FindFirstChild('CameraManager'))
local FriendsData = require(Modules:FindFirstChild('FriendsData'))
local UserData = require(Modules:FindFirstChild('UserData'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local AchievementManager = require(Modules:FindFirstChild('AchievementManager'))
local HeroStatsManager = require(Modules:FindFirstChild('HeroStatsManager'))
local ControllerStateManager = require(Modules:FindFirstChild('ControllerStateManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local TIME_BETWEEN_BACKGROUND_TRANSITIONS = 30
local CROSSFADE_DURATION = 1.5
local BACKGROUND_ASSETS =
{
'Home_screen_01.png';
'Home_screen_02.png';
'Home_screen_03.png';
'Home_screen_04.png';
}
local AppHomeContainer = Utility.Create'Frame'
{
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 0;
BorderSizePixel = 0;
BackgroundColor3 = Color3.new(0,0,0);
Name = 'AppHomeContainer';
Parent = GuiRoot;
}
local BackgroundAssetIndex = 1
local Background = Utility.Create'ImageLabel'
{
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 0;
BorderSizePixel = 0;
Name = 'Background';
Image = 'rbxasset://textures/ui/Shell/Background/' .. BACKGROUND_ASSETS[BackgroundAssetIndex];
Parent = AppHomeContainer;
}
local CrossfadeBackground = Utility.Create'ImageLabel'
{
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
Name = 'CrossfadeBackground';
Image = '';
Parent = Background;
}
Background.ImageTransparency = 0
-- get fast flags for 3D backgrounds
local Is3DBackgroundEnabled = Utility.IsFastFlagEnabled("Durango3DBackground")
-- print ("BACKGROUND")
-- print (Background3DSuccess)
-- print (Background3DValue)
local soundHandle = SoundManager:Play('BackgroundLoop', 0.33, true)
if soundHandle then
local bgmLoopConn = nil
bgmLoopConn = soundHandle.DidLoop:connect(function(soundId, loopCount)
-- print("DidLoop BackgroundLoop" , soundId, "times:", loopCount)
if loopCount >= 1 then
bgmLoopConn = Utility.DisconnectEvent(bgmLoopConn)
if soundHandle then
-- print("Volume fade out")
SoundManager:TweenSound(soundHandle, 0.1, 3)
-- Utility.PropertyTweener(soundHandle, 'Volume', soundHandle.Volume, 0.1, 3, Utility.EaseInOutQuad, true)
end
end
end)
end
if Is3DBackgroundEnabled then
spawn(function()
while true do
local queueSize = ContentProvider.RequestQueueSize
if queueSize == 0 then
CameraManager:StartTransitionScreenEffect()
CameraManager:EnableCameraControl()
spawn(function()
CameraManager:CameraMoveToAsync()
end)
AppHomeContainer.BackgroundTransparency = 1
Background.BackgroundTransparency = 1;
Background.ImageTransparency = 1
CrossfadeBackground.ImageTransparency = 1
Utility.PropertyTweener(Background, 'ImageTransparency', 0, 1, CROSSFADE_DURATION, Utility.EaseInOutQuad, true)
break
end
wait(0.01)
end
end)
else
spawn(function()
CameraManager:StartTransitionScreenEffect()
while true do
wait(TIME_BETWEEN_BACKGROUND_TRANSITIONS)
-- 1-based modulo indexing
BackgroundAssetIndex = ((BackgroundAssetIndex) % #BACKGROUND_ASSETS) + 1
CrossfadeBackground.Image = Background.Image
CrossfadeBackground.ImageTransparency = 0
Background.Image = 'rbxasset://textures/ui/Shell/Background/' .. BACKGROUND_ASSETS[BackgroundAssetIndex];
Background.ImageTransparency = 1
Utility.PropertyTweener(Background, 'ImageTransparency', 1, 0, CROSSFADE_DURATION, Utility.EaseInOutQuad, true)
wait(CROSSFADE_DURATION)
Utility.PropertyTweener(CrossfadeBackground, 'ImageTransparency', 0, 1, CROSSFADE_DURATION, Utility.EaseInOutQuad, true)
end
end)
end
local AspectRatioProtector = Utility.Create'Frame'
{
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
Name = 'AspectRatioProtector';
Parent = CrossfadeBackground;
}
local function OnAbsoluteSizeChanged()
local newSize = Utility.CalculateFit(GuiRoot, Vector2.new(16,9))
if newSize ~= AspectRatioProtector.Size then
AspectRatioProtector.Size = newSize
Utility.CalculateAnchor(AspectRatioProtector, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
end
end
GuiRoot.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
OnAbsoluteSizeChanged()
end
end)
OnAbsoluteSizeChanged()
local ActionSafeContainer = Utility.Create'Frame'
{
Size = UDim2.new(1, 0, 1, 0) - (GlobalSettings.ActionSafeInset + GlobalSettings.ActionSafeInset);
Position = UDim2.new(0,0,0,0) + GlobalSettings.ActionSafeInset;
BackgroundTransparency = 1;
Name = 'ActionSafeContainer';
Parent = AspectRatioProtector;
}
local titleActionSafeDelta = GlobalSettings.TitleSafeInset -- - GlobalSettings.ActionSafeInset
local TitleSafeContainer = Utility.Create'Frame'
{
Size = UDim2.new(1, 0, 1, 0) - (titleActionSafeDelta + titleActionSafeDelta);
Position = UDim2.new(0,0,0,0) + titleActionSafeDelta;
BackgroundTransparency = 1;
Name = 'TitleSafeContainer';
Parent = ActionSafeContainer;
}
local EngagementScreen = EngagementScreenModule()
EngagementScreen:SetParent(TitleSafeContainer)
local userInputConn = nil
local function returnToEngagementScreen()
if ScreenManager:ContainsScreen(EngagementScreen) then
while ScreenManager:GetTopScreen() ~= EngagementScreen do
ScreenManager:CloseCurrent()
end
else
while ScreenManager:GetTopScreen() do
ScreenManager:CloseCurrent()
end
ScreenManager:OpenScreen(EngagementScreen)
end
end
local AppHub = nil
local OverscanScreen = nil
local function onAuthenticationSuccess(isNewLinkedAccount)
-- Set UserData
UserData:Initialize()
-- Unwind Screens if needed - this will be needed once we put in account linking
returnToEngagementScreen()
AppHub = AppHubModule()
AppHub:SetParent(TitleSafeContainer)
EventHub:addEventListener(EventHub.Notifications["OpenGameDetail"], "gameDetail",
function(placeId, placeName, iconId, gameData)
local gameDetail = GameDetailModule(placeId, placeName, iconId, gameData)
gameDetail:SetParent(TitleSafeContainer);
ScreenManager:OpenScreen(gameDetail);
end);
EventHub:addEventListener(EventHub.Notifications["OpenGameGenre"], "gameGenre",
function(sortName, gameCollection)
local gameGenre = GameGenreModule(sortName, gameCollection)
gameGenre:SetParent(TitleSafeContainer);
ScreenManager:OpenScreen(gameGenre);
end);
EventHub:addEventListener(EventHub.Notifications["OpenBadgeScreen"], "gameBadges",
function(badgeData, previousScreenName)
local badgeScreen = BadgeScreenModule(badgeData, previousScreenName)
badgeScreen:SetParent(TitleSafeContainer);
ScreenManager:OpenScreen(badgeScreen);
end)
EventHub:addEventListener(EventHub.Notifications["OpenSocialScreen"], "socialScreen",
function(socialScreen)
socialScreen:SetParent(TitleSafeContainer);
ScreenManager:OpenScreen(socialScreen);
end)
EventHub:addEventListener(EventHub.Notifications["OpenSettingsScreen"], "settingsScreen",
function(settingsScreen)
settingsScreen:SetParent(TitleSafeContainer);
ScreenManager:OpenScreen(settingsScreen);
end)
OverscanScreen = OverscanScreenModule(AppHomeContainer)
EventHub:addEventListener(EventHub.Notifications["OpenOverscanScreen"], "openOverscan", function(data) ScreenManager:OpenScreen(OverscanScreen) end)
Utility.DisconnectEvent(userInputConn)
userInputConn = UserInputService.InputBegan:connect(function(input, processed)
if input.KeyCode == Enum.KeyCode.ButtonX then
-- EventHub:dispatchEvent(EventHub.Notifications["TestXButtonPressed"])
end
end)
-- disable control control
CameraManager:DisableCameraControl()
print("User and Event initialization finished. Opening AppHub")
ScreenManager:OpenScreen(AppHub);
ControllerStateManager:Initialize()
-- retro check
ControllerStateManager:CheckUserConnected()
-- show info popup to users on newly linked accounts
if isNewLinkedAccount == true then
local titleAndMsg = {
Title = Strings:LocalizedString('PlatformLinkInfoTitle');
Msg = Strings:LocalizedString('PlatformLinkInfoMessage');
}
ScreenManager:OpenScreen(ErrorOverlay(titleAndMsg, true), false)
end
end
local function onReAuthentication(reauthenticationReason)
print("Beging Reauth, cleaning things up")
-- unwind ScreenManager
returnToEngagementScreen()
UserData:Reset()
FriendsData.Reset()
AppHub = nil
OverscanScreen = nil
EventHub:removeEventListener(EventHub.Notifications["OpenGameDetail"], "gameDetail")
EventHub:removeEventListener(EventHub.Notifications["OpenGameGenre"], "gameGenre")
EventHub:removeEventListener(EventHub.Notifications["OpenBadgeScreen"], "gameBadges")
EventHub:removeEventListener(EventHub.Notifications["OpenSocialScreen"], "socialScreen")
EventHub:removeEventListener(EventHub.Notifications["OpenOverscanScreen"], "openOverscan")
EventHub:removeEventListener(EventHub.Notifications["OpenSocialScreen"], "socialScreen")
EventHub:removeEventListener(EventHub.Notifications["OpenSettingsScreen"], "settingsScreen")
userInputConn = Utility.DisconnectEvent(userInputConn)
print("Reauth complete. Return to engagement screen.")
-- show reason overlay
local alert = Errors.Reauthentication[reauthenticationReason] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(alert, true), false)
-- re-enable camera control
CameraManager:EnableCameraControl()
end
local function onGameJoin(joinResult)
-- 0 is success, anything else is an error
local joinSuccess = joinResult == 0
if not joinSuccess then
local err = Errors.GameJoin[joinResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
EventHub:dispatchEvent(EventHub.Notifications["GameJoin"], joinSuccess)
end
if PlatformService then
PlatformService.UserAccountChanged:connect(onReAuthentication)
PlatformService.GameJoined:connect(onGameJoin)
end
EventHub:addEventListener(EventHub.Notifications["AuthenticationSuccess"], "authUserSuccess", function(isNewLinkedAccount)
print("User authenticated, initializing app shell")
onAuthenticationSuccess(isNewLinkedAccount)
end);
-- EventHub:addEventListener(EventHub.Notifications["OpenEngagementScreen"], "",
-- function()
-- ScreenManager:OpenScreen(EngagementScreen)
-- end);
ScreenManager:OpenScreen(EngagementScreen)
UserInputService.MouseIconEnabled = false
@@ -0,0 +1,197 @@
--[[
// AccountManager.lua
// Handles all account related functions
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService('UserInputService')
local Http = require(Modules:FindFirstChild('Http'))
local AccountManager = {}
AccountManager.AuthResults = {
Error = -1;
Success = 0;
InProgress = 1;
AccountUnlinked = 2;
MissingGamePad = 3;
NoUserDetected = 4;
HttpErrorDetected = 5;
SignUpDisabled = 6;
Flooded = 7;
LeaseLocked = 8;
AccountLinkingDisabled = 9;
InvalidRobloxUser = 10;
RobloxUserAlreadyLinked = 11;
XboxUserAlreadyLinked = 12;
IllgealChildAccountLinking = 13;
InvalidPassword = 14;
UsernamePasswordNotSet = 15;
UsernameAlreadyTaken = 16;
}
AccountManager.InvalidUsernameReasons = {
Valid = "Valid";
InvalidUsername = "Invalid Username";
AlreadyTaken = "Already Taken";
InvalidCharactersUsed = "Invalid Characters Used";
UsernameCannotContainSpaces = "Username Cannot Contain Spaces";
}
--[[ Authentication ]]--
local function authenticateStudio()
return AccountManager.AuthResults.Success
end
-- TODO: Auth will now return an int that maps to a success or failure
-- Getting the GamerTag and rbxUid will be a seperate call after auth
local function authenticateXBox(inputObject)
local authResult = nil
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
authResult = PlatformService:BeginAuthUnlinkCheck(inputObject)
end)
return authResult
end
function AccountManager:SignInAsync(inputObject)
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginAuthorization(inputObject)
end)
if success then
return result
else
return AccountManager.AuthResults.Error
end
end
-- Returns
-- Success of authentication, with authentication state
function AccountManager:BeginAuthenticationAsync(inputObject)
local result = nil
if UserSettings().GameSettings:InStudioMode() then
result = authenticateStudio()
elseif PlatformService and UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
result = authenticateXBox(inputObject)
end
return result
end
--[[ Account Linking ]]--
-- called at sign in
function AccountManager:LinkAccountAsync(accountName, password)
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginAccountLink(accountName, password)
end)
if not success then
print("AccountManager:LinkAccountAsync() failed because", result)
result = AccountManager.AuthResults.Error
end
return result
end
-- used when setting credentials for a generated account
function AccountManager:SetRobloxCredentialsAsync(accountName, password)
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginSetRobloxCredentials(accountName, password)
end)
if not success then
print("AccountManager:SetRobloxCredentialsAsync() failed because", result)
result = AccountManager.AuthResults.Error
end
return result
end
-- used when creating a new roblox account that is linked to the users xbox account
function AccountManager:GenerateAccountAsync(accountName, password)
local result = AccountManager:SignInAsync(Enum.UserInputType.Gamepad1)
if result == AccountManager.AuthResults.Success then
result = AccountManager:SetRobloxCredentialsAsync(accountName, password)
end
return result
end
-- called when user has roblox credentials
function AccountManager:UnlinkAccountAsync()
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginUnlinkAccount()
end)
if not success then
print("AccountManager:UnlinkAccountAsync() failed because", result)
result = AccountManager.AuthResults.Error
end
return result
end
function AccountManager:HasLinkedAccountAsync()
if UserSettings().GameSettings:InStudioMode() then
return AccountManager.AuthResults.Success
end
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginHasLinkedAccount()
end)
if not success then
print("AccountManager:HasLinkedAccountAsync() failed because", result)
result = AccountManager.AuthResults.Error
end
return result
end
function AccountManager:HasRobloxCredentialsAsync()
if UserSettings().GameSettings:InStudioMode() then
return AccountManager.AuthResults.Success
end
local success, result = pcall(function()
-- PlatformService may not exist on studio platform
return PlatformService:BeginHasRobloxCredentials()
end)
if not success then
print("AccountManager:HasRobloxCredentialsAsync() failed because", result)
result = AccountManager.AuthResults.Error
end
return result
end
function AccountManager:IsValidUsernameAsync(username)
local result = Http.IsValidUsername(username)
if not result then
-- return false
return nil
end
return result["IsValid"], result["ErrorMessage"]
end
function AccountManager:IsValidPasswordAsync(username, password)
local result = Http.IsValidPassword(username, password)
if not result then
-- return false
return nil
end
return result["IsValid"], result["ErrorMessage"]
end
return AccountManager
@@ -0,0 +1,51 @@
--[[
// AccountPage.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService('ContextActionService')
local GuiService = game:GetService('GuiService')
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseScreen = require(Modules:FindFirstChild('BaseScreen'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Utility = require(Modules:FindFirstChild('Utility'))
local UserData = require(Modules:FindFirstChild('UserData'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local SetAccountCredentialsScreen = require(Modules:FindFirstChild('SetAccountCredentialsScreen'))
local UnlinkAccountScreen = require(Modules:FindFirstChild('UnlinkAccountScreen'))
local LinkAccountScreen = require(Modules:FindFirstChild('LinkAccountScreen'))
-- This is an empty page that is a place holder. Account page changes depending on cases
local function createAccountScreen()
local hasLinkedAccount = UserData:HasLinkedAccount()
local hasRobloxCredentials = UserData:HasRobloxCredentials()
-- Cases
-- 1. Has roblox credentials, which implies they have a linked account
-- 2. No credentials but a linked account
-- 3. No Credentials/No Linked account - this should never happen, but cover it
-- 4. One of these calls has a web error, result will be nil in that case
local this = nil
if hasRobloxCredentials ~= nil and hasLinkedAccount ~= nil then
if hasRobloxCredentials == true then
this = UnlinkAccountScreen()
elseif hasLinkedAccount == true and hasRobloxCredentials == false then
this = SetAccountCredentialsScreen(Strings:LocalizedString("SignUpTitle"),
Strings:LocalizedString("SignUpPhrase"), Strings:LocalizedString("SignUpWord"))
end
end
return this
end
return createAccountScreen
@@ -0,0 +1,262 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
-- Achievement Manager
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local EventHub = require(Modules:FindFirstChild('EventHub'))
local Http = require(Modules:FindFirstChild('Http'))
local UserData = require(Modules:FindFirstChild('UserData'))
local PlatformInterface = require(Modules:FindFirstChild('PlatformInterface'))
local GameCollection = require(Modules:FindFirstChild('GameCollection'))
--[[ ACHIEVEMENT NAMES --]]
-- "Award10DayRoll"
-- "Award20DayRoll"
-- "Award3DayRoll"
-- "AwardDeepDiver"
-- "AwardFoursCompany"
-- "AwardOneNameManyFaces"
-- "AwardPollster"
-- "AwardSampler"
-- "AwardStrengthInNumbers"
-- "AwardWorldTraveler"
-- "AwardYouDidIt"
-- "GameProgress"
-- "MultiplayerRoundEnd"
-- "MultiplayerRoundStart"
-- "PlayerSessionEnd"
-- "PlayerSessionPause"
-- "PlayerSessionResume"
-- "PlayerSessionStart"
-- "Test_XPresses"
--[[ END OF ACHIEVEMENT NAMES --]]
local VIEW_GAMETYPE_ENUM =
{
AppShell = 0;
Game = 1;
}
local GAMES_FOR_YOU_DID_IT = 1
local GAMES_FOR_AWARD_SAMPLER = 5
local DAYS_FOR_3DAYROLL = 3
local DAYS_FOR_10DAYROLL = 10
local DAYS_FOR_20DAYROLL = 20
local GAMES_RATED_FOR_POLLSTER = 5
local PLAY_SECONDS_FOR_DEEP_DIVER = 60 * 60
local NUMBER_OF_FRIENDS_REQUIRED_FOR_FOURS_COMPANY = 3
local SECONDS_BETWEEN_FOURS_COMPANY_CHECKS = 30
local AchievementManager = {}
local CurrentView = VIEW_GAMETYPE_ENUM['AppShell']
local function GetTotalNumberOfGamesOnXbox()
-- TODO: is there a programmatic way of figuring this out?
-- local SortData = require(SortDataModule)
-- local recentlyPlayedSortData = SortData.GetSort(1, false)
return 15
end
local function FilterInGameFriends(onlineFriends, playersInGame)
local result = {}
if onlineFriends and playersInGame then
-- Create reverse lookup for speed
local playersInGameReverseLookup = {}
for _, playerInGame in pairs(playersInGame) do
-- TODO: Figure out what the actual lookup
if playerInGame['robloxuid'] then
playersInGameReverseLookup[playerInGame['robloxuid']] = true
end
end
for _, friend in pairs(onlineFriends) do
if playersInGameReverseLookup[friend['robloxuid']] then
table.insert(result, friend)
end
end
end
return result
end
local function OnPlayedGamesChanged()
spawn(function()
local myUserId = UserData:GetRbxUserId()
if myUserId then
local recentCollection = GameCollection:GetUserRecent()
-- TODO: is this the right way of getting num of played games?
local recentlyPage1 = recentCollection and recentCollection:GetSortAsync(0, GetTotalNumberOfGamesOnXbox())
local gamesPlayed = recentlyPage1 and #recentlyPage1:GetPagePlaceIds() or 0
print("You have played:" , gamesPlayed , "games" )
if gamesPlayed >= GAMES_FOR_YOU_DID_IT then
AchievementManager:SendAchievementEventAsync("AwardYouDidIt")
end
if gamesPlayed >= GAMES_FOR_AWARD_SAMPLER then
AchievementManager:SendAchievementEventAsync("AwardSampler")
end
if gamesPlayed >= GetTotalNumberOfGamesOnXbox() then
AchievementManager:SendAchievementEventAsync("AwardWorldTraveler")
end
end
end)
end
local function OnJoinedGame()
spawn(function()
print("OnJoinGame: AwardStrengthInNumbers check")
local partyMembers = PlatformInterface:GetPartyMembersAsync()
if partyMembers then
if PlatformInterface:IsInAParty(partyMembers) then
AchievementManager:SendAchievementEventAsync("AwardStrengthInNumbers")
end
end
end)
spawn(function()
print("OnJoinGame: Fours Company check")
if PlatformService then
local lastCheck = 0
while CurrentView == VIEW_GAMETYPE_ENUM['Game'] do
local now = tick()
if now - lastCheck > SECONDS_BETWEEN_FOURS_COMPANY_CHECKS then
local friendsData = require(Modules:FindFirstChild('FriendsData'))
local onlineFriends = friendsData.GetOnlineFriendsAsync()
-- TODO: add actually API
local inGamePlayers = PlatformService:GetInGamePlayers()
if inGamePlayers and onlineFriends then
local inGameFriends = FilterInGameFriends(onlineFriends, inGamePlayers)
if #inGameFriends >= NUMBER_OF_FRIENDS_REQUIRED_FOR_FOURS_COMPANY then
AchievementManager:SendAchievementEventAsync("AwardFoursCompany")
return
end
end
lastCheck = now
end
wait(1)
end
end
end)
spawn(function()
local startTime = tick()
while tick() - startTime < PLAY_SECONDS_FOR_DEEP_DIVER do
if CurrentView ~= VIEW_GAMETYPE_ENUM['Game'] then
return
end
wait(1)
end
if CurrentView == VIEW_GAMETYPE_ENUM['Game'] then
AchievementManager:SendAchievementEventAsync("AwardDeepDiver")
end
end)
end
function AchievementManager:SendAchievementEventAsync(achievementName)
print("Achievement Manager - Awarding achievement:" , achievementName)
local achievementStatus = nil
local success, msg = pcall(function()
-- NOTE: Yielding function
if not UserSettings().GameSettings:InStudioMode() then
achievementStatus = PlatformService:BeginAwardAchievement(achievementName)
end
end)
if not success then
-- NOTE: very likely this function ever throws an error but returns error codes
print("Achievement Manager - Unable to award achievement:" , achievementName , "for reason:" , msg)
end
print("Achievement Manager - Achievement:" , achievementName , "event status:" , achievementStatus)
end
EventHub:addEventListener(EventHub.Notifications["TestXButtonPressed"], "AchievementManager",
function()
-- AchievementManager:SendAchievementEventAsync("Test_XPresses")
end)
EventHub:addEventListener(EventHub.Notifications["AuthenticationSuccess"], "AchievementManager",
function()
spawn(function()
local myUserId = UserData:GetRbxUserId()
local function stillLoggedIn()
local newUserId = UserData:GetRbxUserId()
return newUserId ~= nil and myUserId == newUserId
end
if myUserId ~= nil then
local loggedInResult = Http.GetConsecutiveDaysLoggedInAsync()
local daysLoggedIn = loggedInResult and loggedInResult['count']
-- local Utility = require(Modules:FindFirstChild('Utility'))
-- print("AchievementManager - Checking number of days Logged in:" , Utility.PrettyPrint(loggedInResult))
if daysLoggedIn then
if daysLoggedIn >= DAYS_FOR_3DAYROLL and stillLoggedIn() then
AchievementManager:SendAchievementEventAsync("Award3DayRoll")
end
if daysLoggedIn >= DAYS_FOR_10DAYROLL and stillLoggedIn() then
AchievementManager:SendAchievementEventAsync("Award10DayRoll")
end
if daysLoggedIn >= DAYS_FOR_20DAYROLL and stillLoggedIn() then
AchievementManager:SendAchievementEventAsync("Award20DayRoll")
end
end
OnPlayedGamesChanged()
end
end)
end)
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentPackage"], "AchievementManager",
function(assetId)
AchievementManager:SendAchievementEventAsync("AwardOneNameManyFaces")
end)
EventHub:addEventListener(EventHub.Notifications["VotedOnPlace"], "AchievementManager",
function()
spawn(function()
local voteCount = UserData:GetVoteCount()
print("Vote Check: with vote count" , voteCount)
if voteCount >= GAMES_RATED_FOR_POLLSTER then
AchievementManager:SendAchievementEventAsync("AwardPollster")
end
end)
end)
if PlatformService then
PlatformService.ViewChanged:connect(function(newView)
print("ViewChanged:" , newView)
CurrentView = newView
if newView == VIEW_GAMETYPE_ENUM['AppShell'] then
print("New view is appshell")
OnPlayedGamesChanged()
elseif newView == VIEW_GAMETYPE_ENUM['Game'] then
print("New view is game")
OnJoinedGame()
end
end)
end
return AchievementManager
@@ -0,0 +1,352 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = game:GetService("CoreGui")
local ContextActionService = game:GetService("ContextActionService")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService('GuiService')
local TextService = game:GetService('TextService')
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local AppTabDockModule = require(Modules:FindFirstChild('TabDock'))
local AppTabDockItemModule = require(Modules:FindFirstChild('TabDockItem'))
local HomePaneModule = require(Modules:FindFirstChild('HomePane'))
local GamePaneModule = require(Modules:FindFirstChild('GamePane'))
local AvatarPaneModule = require(Modules:FindFirstChild('AvatarPane'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SocialPaneModule = require(Modules:FindFirstChild('SocialPane'))
local StorePaneModule = require(Modules:FindFirstChild('StorePane'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local SettingsScreen = require(Modules:FindFirstChild('SettingsScreen'))
local function CreateAppHub()
local this = {}
-- Game.CoreGui.SelectionImageObject = Instance.new('ImageLabel')
local AppTabDock = AppTabDockModule()
local appHubCns = {}
local selectionChangedConn = nil
local lastSelectedContentPane = nil
local lastParent = nil
local HubContainer = Utility.Create'Frame'
{
Name = 'HubContainer';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
}
local PaneContainer = Utility.Create'Frame'
{
Name = 'PaneContainer';
Size = UDim2.new(1, 0, 0.786, 0);
Position = UDim2.new(0,0,0.214,0);
BackgroundTransparency = 1;
Parent = HubContainer;
}
AppTabDock:SetParent(HubContainer)
AppTabDock:SetPosition(UDim2.new(0,0,0.132,0))
local HomeTab = AppTabDock:AddTab(AppTabDockItemModule(Strings:LocalizedString('HomeWord'):upper(), HomePaneModule(PaneContainer)))
local AvatarTab = AppTabDock:AddTab(AppTabDockItemModule(Strings:LocalizedString('AvatarWord'):upper(), AvatarPaneModule(PaneContainer)))
local GameTab = AppTabDock:AddTab(AppTabDockItemModule(Strings:LocalizedString('GameWord'):upper(), GamePaneModule(PaneContainer)))
local SocialTab = AppTabDock:AddTab(AppTabDockItemModule(Strings:LocalizedString('FriendsWord'):upper(), SocialPaneModule(PaneContainer)))
local StoreTab = AppTabDock:AddTab(AppTabDockItemModule(Strings:LocalizedString('CatalogWord'):upper(), StorePaneModule(PaneContainer)))
local RobloxLogo = Utility.Create'ImageLabel'
{
Name = 'RobloxLogo';
Size = UDim2.new(0, 232, 0, 56);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/ROBLOXLogoSmall@1080.png';
Parent = HubContainer;
}
-- Positin/Size set after text bounds set
local BoundHintContainer = Utility.Create'Frame'
{
Name = "BoundHintContainer";
BackgroundTransparency = 1;
Visible = false;
Parent = HubContainer;
}
local BoundHintText = Utility.Create'TextLabel'
{
Name = "BoundHintText";
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = GlobalSettings.Right;
Text = "";
Parent = BoundHintContainer;
}
local BoundHintImage = Utility.Create'ImageLabel'
{
Name = "BoundHintImage";
Size = UDim2.new(0, 83, 0, 83);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/XButton.png';
Parent = BoundHintContainer;
}
local function setHintText(newText)
local textSize = TextService:GetTextSize(newText, 42, BoundHintText.Font, Vector2.new(0, 0))
BoundHintText.Size = UDim2.new(0, textSize.x, 0, 83)
BoundHintText.Position = UDim2.new(1, -textSize.x, 0, 0)
BoundHintText.Text = newText
BoundHintContainer.Size = UDim2.new(0, textSize.x + BoundHintImage.Size.X.Offset, 0, 83)
BoundHintContainer.Position = UDim2.new(1, -BoundHintContainer.Size.X.Offset, 1, -BoundHintContainer.Size.Y.Offset)
BoundHintContainer.Visible = true
end
local seenXButtonPressed = false
local function onOpenSettings(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenXButtonPressed = true
elseif inputState == Enum.UserInputState.End and seenXButtonPressed then
local settingsScreen = SettingsScreen()
EventHub:dispatchEvent(EventHub.Notifications["OpenSettingsScreen"], settingsScreen);
end
end
local function onOpenPartyUI(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenXButtonPressed = true
elseif inputState == Enum.UserInputState.End and seenXButtonPressed then
if UserSettings().GameSettings:InStudioMode() then
ScreenManager:OpenScreen(ErrorOverlay(Errors.Test.FeatureNotAvailableInStudio), false)
else
local success, result = pcall(function()
-- PlatformService may not exist in studio
return PlatformService:PopupPartyUI(inputObject.UserInputType)
end)
if not success then
ScreenManager:OpenScreen(ErrorOverlay(Errors.PlatformError.PopupPartyUI), false)
end
end
end
end
local function setHintAction(selectedTab)
ContextActionService:UnbindCoreAction("OpenHintAction")
BoundHintContainer.Visible = false
if selectedTab == HomeTab then
setHintText(Strings:LocalizedString("SettingsWord"))
ContextActionService:BindCoreAction("OpenHintAction", onOpenSettings, false, Enum.KeyCode.ButtonX)
elseif selectedTab == SocialTab then
setHintText(Strings:LocalizedString("StartPartyPhrase"))
ContextActionService:BindCoreAction("OpenHintAction", onOpenPartyUI, false, Enum.KeyCode.ButtonX)
end
end
function this:GetName()
-- print("Apphub get name called - lastSelectedContentPane:" , lastSelectedContentPane, "name:" , lastSelectedContentPane and lastSelectedContentPane:GetName())
return lastSelectedContentPane and lastSelectedContentPane:GetName() or Strings:LocalizedString('HomeWord')
end
function this:Show()
HubContainer.Visible = true
HubContainer.Parent = lastParent
EventHub:removeEventListener(EventHub.Notifications["NavigateToRobuxScreen"], 'AppHubListenToRobuxScreenSwitch')
EventHub:addEventListener(EventHub.Notifications["NavigateToRobuxScreen"], 'AppHubListenToRobuxScreenSwitch', function()
if ScreenManager:ContainsScreen(this) then
while ScreenManager:GetTopScreen() ~= this and ScreenManager:ContainsScreen(this) do
ScreenManager:CloseCurrent()
end
if ScreenManager:GetTopScreen() == this then
if AppTabDock:GetSelectedTab() ~= StoreTab then
AppTabDock:SetSelectedTab(StoreTab)
end
end
end
end)
local openEquippedDebounce = false
EventHub:removeEventListener(EventHub.Notifications["NavigateToEquippedAvatar"], 'AppHubListenToAvatarScreenSwitch')
EventHub:addEventListener(EventHub.Notifications["NavigateToEquippedAvatar"], 'AppHubListenToAvatarScreenSwitch', function()
if openEquippedDebounce then return end
openEquippedDebounce = true
if ScreenManager:ContainsScreen(this) then
while ScreenManager:GetTopScreen() ~= this and ScreenManager:ContainsScreen(this) do
ScreenManager:CloseCurrent()
end
if ScreenManager:GetTopScreen() == this then
if AppTabDock:GetSelectedTab() ~= AvatarTab then
AppTabDock:SetSelectedTab(AvatarTab)
-- local avatarPane = AvatarTab:GetContentItem()
-- if avatarPane then
-- avatarPane:OpenEquippedPackage()
-- end
end
end
end
openEquippedDebounce = false
end)
local currentlySelectedTab = AppTabDock:GetSelectedTab()
AppTabDock:SetSelectedTab(currentlySelectedTab)
if lastSelectedContentPane then
lastSelectedContentPane:Show()
end
end
function this:Hide()
if not ScreenManager:ContainsScreen(self) then
EventHub:removeEventListener(EventHub.Notifications["NavigateToRobuxScreen"], 'AppHubListenToRobuxScreenSwitch')
EventHub:removeEventListener(EventHub.Notifications["NavigateToEquippedAvatar"], 'AppHubListenToAvatarScreenSwitch')
end
HubContainer.Visible = false
HubContainer.Parent = nil
if lastSelectedContentPane then
lastSelectedContentPane:Hide()
end
end
function this:Focus()
AppTabDock:ConnectEvents()
ContextActionService:BindCoreAction("CycleTabDock",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
if inputObject.KeyCode == Enum.KeyCode.ButtonL1 then
local prevTab = AppTabDock:GetPreviousTab()
if prevTab then
AppTabDock:SetSelectedTab(prevTab)
end
elseif inputObject.KeyCode == Enum.KeyCode.ButtonR1 then
local nextTab = AppTabDock:GetNextTab()
if nextTab then
AppTabDock:SetSelectedTab(nextTab)
end
end
end
end,
false,
Enum.KeyCode.ButtonL1, Enum.KeyCode.ButtonR1)
local seenBButtonBegin = false
ContextActionService:BindCoreAction("CloseAppHub",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenBButtonBegin = true
elseif inputState == Enum.UserInputState.End then
if seenBButtonBegin then
local currentlySelectedTab = AppTabDock:GetSelectedTab()
if currentlySelectedTab ~= HomeTab then
AppTabDock:SetSelectedTab(HomeTab)
end
end
end
end,
false,
Enum.KeyCode.ButtonB)
local function onSelectedTabChanged(selectedTab)
if selectedTab then
if lastSelectedContentPane then
lastSelectedContentPane:Hide()
lastSelectedContentPane:RemoveFocus()
end
local selectedContentPane = selectedTab:GetContentItem()
if selectedContentPane then
selectedContentPane:Show()
if not AppTabDock:IsFocused() then
AppTabDock:Focus()
selectedContentPane:Focus()
end
end
lastSelectedContentPane = selectedContentPane
-- set X action
setHintAction(selectedTab)
end
end
table.insert(appHubCns, AppTabDock.SelectedTabChanged:connect(onSelectedTabChanged))
local function onSelectedTabClicked(selectedTab)
local selectedContentPane = selectedTab and selectedTab:GetContentItem()
if selectedContentPane and selectedContentPane == lastSelectedContentPane then
selectedContentPane:Focus()
end
end
table.insert(appHubCns, AppTabDock.SelectedTabClicked:connect(onSelectedTabClicked))
selectionChangedConn = Utility.DisconnectEvent(selectionChangedConn)
selectionChangedConn = GuiService.Changed:connect(function(prop)
if prop == "SelectedCoreObject" then
local currentSelection = GuiService.SelectedCoreObject
if currentSelection and lastSelectedContentPane then
-- first condition checks if function exist
if lastSelectedContentPane.IsFocused and not lastSelectedContentPane:IsFocused() and lastSelectedContentPane.IsAncestorOf then
if lastSelectedContentPane:IsAncestorOf(currentSelection) then
-- print("Doing our focus")
lastSelectedContentPane:Focus()
else
-- lastSelectedContentPane:RemoveFocus()
end
end
end
end
end)
--set the Home tab to be the starting tab
if AppTabDock:GetSelectedTab() == nil then
AppTabDock:SetSelectedTab(HomeTab)
end
if lastSelectedContentPane then
lastSelectedContentPane:Focus()
end
setHintAction(AppTabDock:GetSelectedTab())
end
function this:RemoveFocus()
AppTabDock:DisconnectEvents()
ContextActionService:UnbindCoreAction("CycleTabDock")
ContextActionService:UnbindCoreAction("CloseAppHub")
if lastSelectedContentPane then
lastSelectedContentPane:RemoveFocus()
end
for k,v in pairs(appHubCns) do
v:disconnect()
v = nil
appHubCns[k] = nil
end
selectionChangedConn = Utility.DisconnectEvent(selectionChangedConn)
ContextActionService:UnbindCoreAction("OpenHintAction")
end
function this:SetParent(newParent)
HubContainer.Parent = newParent
lastParent = newParent
end
local hubID = "AppHub"
--EventHub:addEventListener(EventHub.Notifications["OpenGameDetail"], hubID, function(data) AppTabDock:SetSelectedTab(GameTab); end);
--EventHub:addEventListener(EventHub.Notifications["OpenGameGenre"], hubID, function(data) AppTabDock:SetSelectedTab(GameTab); end);
return this
end
return CreateAppHub
@@ -0,0 +1,134 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local GuiService = game:GetService('GuiService')
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local AssetManager = {}
local TrackedAssets = {}
local WeakTrackedAssets = {}
-- Set weak-keys table
setmetatable(WeakTrackedAssets, {__mode = 'k' })
local function GetScreenSize()
-- return GuiService:GetScreenResolution()
return GuiRoot.AbsoluteSize
end
local function GetImageSuffixByScreenSize(screenSize)
if screenSize.Y <= 720 then
return '@720'
else
return '@1080'
end
end
local function UpdateAsset(asset, metadata)
if asset then
local newSize = GetScreenSize()
local newScreenSuffix = GetImageSuffixByScreenSize(newSize)
local imagePath = metadata['path'] .. newScreenSuffix .. metadata['extension']
asset.Image = imagePath
if metadata['sizes'] then
if newScreenSuffix == '@720' and metadata['sizes']['720'] then
asset.Size = metadata['sizes']['720']
elseif newScreenSuffix == '@1080' and metadata['sizes']['1080'] then
asset.Size = metadata['sizes']['1080']
end
end
end
end
local function OnAncestryChanged(descendant, descendantMetatable)
descendantMetatable = descendantMetatable or WeakTrackedAssets[descendant] or TrackedAssets[descendant]
if descendantMetatable then
if descendant.Parent == nil then
WeakTrackedAssets[descendant] = descendantMetatable
TrackedAssets[descendant] = nil
else
TrackedAssets[descendant] = descendantMetatable
WeakTrackedAssets[descendant] = nil
UpdateAsset(descendant, descendantMetatable)
end
end
end
-- rbxImageInstance is a gui object such as ImageButton or ImageLabel
-- Imagepath is the local path to the image
-- fileFormat is the file extension i.e. .png
function AssetManager.LocalImage(rbxImageInstance, imagepath, sizes, fileFormat)
fileFormat = fileFormat or '.png'
if imagepath then
local metadataTable = {
['path'] = imagepath;
['extension'] = fileFormat;
['sizes'] = sizes;
}
UpdateAsset(rbxImageInstance, metadataTable)
OnAncestryChanged(rbxImageInstance, metadataTable)
end
return rbxImageInstance
end
function AssetManager.CreateShadow(zIndex)
return Utility.Create'ImageLabel'
{
Name = 'Shadow';
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleShadow.png';
Size = UDim2.new(1,3,1,3);
Position = UDim2.new(0,0,0,0);
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(10,10,28,28);
BackgroundTransparency = 1;
ZIndex = zIndex or 1;
}
end
local LastScreenSuffix = GetImageSuffixByScreenSize(GetScreenSize())
GuiRoot.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
local newSize = GetScreenSize()
local newScreenSuffix = GetImageSuffixByScreenSize(newSize)
if newScreenSuffix ~= LastScreenSuffix then
for asset, metadata in pairs(TrackedAssets) do
UpdateAsset(asset, metadata)
end
LastScreenSuffix = newScreenSuffix
end
end
end)
GuiRoot.DescendantAdded:connect(function(descendant)
if WeakTrackedAssets[descendant] or TrackedAssets[descendant] then
-- need to spawn because we haven't got the new parent yet
spawn(function()
if descendant then
OnAncestryChanged(descendant)
end
end)
end
end)
GuiRoot.DescendantRemoving:connect(function(descendant)
if WeakTrackedAssets[descendant] or TrackedAssets[descendant] then
-- need to spawn because we haven't got the new parent yet
spawn(function()
if descendant then
OnAncestryChanged(descendant)
end
end)
end
end)
return AssetManager
@@ -0,0 +1,691 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local TextService = game:GetService('TextService')
local CoreGui = game:GetService("CoreGui")
local GuiService = game:GetService('GuiService')
local RunService = game:GetService('RunService')
local ContextActionService = game:GetService("ContextActionService")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PackageData = require(Modules:FindFirstChild('PackageData'))
local OutfitData = require(Modules:FindFirstChild('OutfitData'))
local ScrollingGridModule = require(Modules:FindFirstChild('ScrollingGrid'))
local AvatarTile = require(Modules:FindFirstChild('AvatarTile'))
local OutfitTile = require(Modules:FindFirstChild('OutfitTile'))
local Utility = require(Modules:FindFirstChild('Utility'))
local Http = require(Modules:FindFirstChild('Http'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local UserData = require(Modules:FindFirstChild('UserData'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local GLOW_BASE_RPM = 2
local GLOW_TOP_RPM = -0.5
local GLOW_TRANSPARENCY = 0.2
local CATALOG_BELOW_POSITION = 320
local function CreateAvatarPane(parent)
local this = {}
local inFocus = false
local isShown = false
local AvatarObjects = {}
local OnGuiServiceChangedConn = nil
local lastParent = parent
local LastWearingPackageAssetId = nil
local LastWearingOutfitId = nil
local MainContainer = Utility.Create'Frame'
{
Name = 'AvatarPane';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
}
local MyAvatarContainer = Utility.Create'Frame'
{
Name = 'MyAvatarContainer';
Size = UDim2.new(0.38,0,1,0);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
ClipsDescendants = true;
Parent = MainContainer;
}
local MyNameLabel = Utility.Create'TextLabel'
{
Name = 'MyNameLabel';
Text = '';
Size = UDim2.new(1,0,0,25);
Position = UDim2.new(0,12,0,0);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
BackgroundTransparency = 1;
Parent = MyAvatarContainer;
};
local ProfileImageContainer = Utility.Create'Frame'
{
Name = 'ProfileImageContainer';
Size = UDim2.new(0.68,0,0.9,-MyNameLabel.Size.Y.Offset);
Position = UDim2.new(0.16, 0, 0.05, MyNameLabel.Size.Y.Offset);
BackgroundTransparency = 1;
Parent = MyAvatarContainer;
}
local ProfileImage = Utility.Create'ImageLabel'
{
Name = 'ProfileImage';
Size = UDim2.new(0,780,0,780);
Position = UDim2.new(0.5, 0, 0.5, 0);
BackgroundTransparency = 1;
ZIndex = 3;
Parent = ProfileImageContainer;
};
local CrossfadeProfileImage = Utility.Create'ImageLabel'
{
Name = 'CrossfadeProfileImage';
Size = UDim2.new(1,0,1,0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ImageTransparency = 1;
ZIndex = 3;
Parent = ProfileImage;
};
local CharacterGlowBase = Utility.Create'ImageLabel'
{
Name = 'CharacterGlowBase';
Size = UDim2.new(0,1015,0,1002);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Images/CharacterGlow/CharacterGlowBase.png';
ImageTransparency = GLOW_TRANSPARENCY;
Parent = CrossfadeProfileImage;
};
Utility.CalculateAnchor(CharacterGlowBase, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
local CharacterGlowTop = Utility.Create'ImageLabel'
{
Name = 'CharacterGlowTop';
Size = UDim2.new(0,1026,0,1009);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Images/CharacterGlow/CharacterGlowTop.png';
ImageTransparency = GLOW_TRANSPARENCY;
ZIndex = 2;
Parent = CrossfadeProfileImage;
};
Utility.CalculateAnchor(CharacterGlowTop, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
local function onSizeChanged()
ProfileImage.Size = Utility.CalculateFill(ProfileImage, Vector2.new(576, 324))
Utility.CalculateAnchor(ProfileImage, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
end
local EquipButtonImage = Utility.Create'ImageLabel'
{
Name = 'EquipButtonImage';
Size = UDim2.new(0,70,0,70);
Position = UDim2.new(0.5, 25, 0.75, 0);
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/XButton.png';
ImageTransparency = 1;
BackgroundTransparency = 1;
Parent = MainContainer;
};
local EquipHint = Utility.Create'TextLabel'
{
Name = 'EquipHint';
Text = Strings:LocalizedString('EquipWord'):upper();
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1, 5, 0, -3);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
TextTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
BackgroundTransparency = 1;
Parent = EquipButtonImage;
};
do
local equipHintTextSize = TextService:GetTextSize(EquipHint.Text, Utility.ConvertFontSizeEnumToInt(EquipHint.FontSize), EquipHint.Font, Vector2.new())
Utility.CalculateAnchor(EquipButtonImage, UDim2.new(0.95, -equipHintTextSize.X,1,0), Utility.Enum.Anchor.BottomRight)
end
local function TweenEquipButton(newValue, duration)
duration = duration or 0.25
Utility.PropertyTweener(EquipButtonImage, 'ImageTransparency', EquipButtonImage.ImageTransparency, newValue, duration, Utility.EaseOutQuad, true)
Utility.PropertyTweener(EquipHint, 'TextTransparency', EquipHint.TextTransparency, newValue, duration, Utility.EaseOutQuad, true)
end
local function UpdateEquipButton(IsOwned, IsWearing)
TweenEquipButton((IsOwned and not IsWearing) and 0 or 1)
end
local SelectableAvatarsContainer = Utility.Create'Frame'
{
Name = 'SelectableAvatarsContainer';
Size = UDim2.new(0.6,0,1,0);
Position = UDim2.new(0.4,0,0,0);
BackgroundTransparency = 1;
Parent = MainContainer;
}
local NoCatalogStatusMessage = Utility.Create'TextLabel'
{
Name = 'NoCatalogStatusMessage';
Text = Strings:LocalizedString('DefaultErrorPhrase');
Size = UDim2.new(0.9,0,1,-125);
Position = UDim2.new(0.05, 0, 0, 0);
TextColor3 = GlobalSettings.GreyTextColor;
TextWrapped = true;
TextTransparency = GlobalSettings.FriendStatusTextTransparency;
Font = GlobalSettings.BoldFont;
FontSize = GlobalSettings.DescriptionSize;
BackgroundTransparency = 1;
Visible = false;
Parent = SelectableAvatarsContainer;
};
local OutfitsTitle = Utility.Create'TextLabel'
{
Name = 'OutfitsTitle';
Text = Strings:LocalizedString('AvatarOutfitsTitle'):upper();
Size = UDim2.new(1,0,0,40);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
BackgroundTransparency = 1;
Visible = false;
Parent = SelectableAvatarsContainer;
};
local OutfitsScroller = ScrollingGridModule()
OutfitsScroller:SetSize(UDim2.new(1,0,1,-OutfitsTitle.Size.Y.Offset - 40))
OutfitsScroller:SetScrollDirection(OutfitsScroller.Enum.ScrollDirection.Horizontal)
OutfitsScroller:SetCellSize(Vector2.new(220, 220))
OutfitsScroller:SetSpacing(Vector2.new(25,25))
OutfitsScroller:SetPosition(UDim2.new(0,0,0,OutfitsTitle.Size.Y.Offset))
OutfitsScroller:SetRowColumnConstraint(1)
-- OutfitsScroller:SetParent(SelectableAvatarsContainer)
local CatalogTitle = Utility.Create'TextLabel'
{
Name = 'CatalogTitle';
Text = Strings:LocalizedString('AvatarCatalogTitle'):upper();
Size = UDim2.new(1,0,0,40);
Position = UDim2.new(0,0,0,0);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
BackgroundTransparency = 1;
Parent = SelectableAvatarsContainer;
};
local AvatarScroller = ScrollingGridModule()
AvatarScroller:SetSize(UDim2.new(1,0,1,-CatalogTitle.Size.Y.Offset - 40))
AvatarScroller:SetScrollDirection(AvatarScroller.Enum.ScrollDirection.Horizontal)
AvatarScroller:SetCellSize(Vector2.new(220, 220))
AvatarScroller:SetPosition(UDim2.new(0,0,0,40))
AvatarScroller:SetSpacing(Vector2.new(25,25))
AvatarScroller:SetRowColumnConstraint(2)
AvatarScroller:SetParent(SelectableAvatarsContainer)
local function SortOutfitsScroller()
OutfitsScroller:SortItems(
function(a, b)
local aObject = a and AvatarObjects[a] and AvatarObjects[a]:GetPackageInfo()
local bObject = b and AvatarObjects[b] and AvatarObjects[b]:GetPackageInfo()
local aIsEquipped = aObject and aObject:IsWearing()
local bIsEquipped = bObject and bObject:IsWearing()
local aName = aObject and aObject:GetName()
local bName = bObject and bObject:GetName()
if aIsEquipped then return true elseif bIsEquipped then return false end
if aName and bName then
return aName < bName
end
return aObject ~= nil
end)
end
local function OnAddToOutfitsScroller()
CatalogTitle.Position = UDim2.new(0,0,0, CATALOG_BELOW_POSITION)
AvatarScroller:SetPosition(UDim2.new(0,0,0,CATALOG_BELOW_POSITION + 40))
AvatarScroller:SetRowColumnConstraint(1)
OutfitsTitle.Visible = true
OutfitsScroller:SetParent(SelectableAvatarsContainer)
SortOutfitsScroller()
end
local LoaderSpinner = nil
local ProfileImageThumbnailLoader = nil
local GlobalFadeCount = 1
local function CrossfadeAvatarImage(frontImage, fadeImage, newImageUrl, duration)
duration = duration or 0.75
GlobalFadeCount = GlobalFadeCount + 1
local thisFadeCount = GlobalFadeCount
local fadeoutDuration = duration
spawn(function()
local dummyImage = {}
local function waitForWearReady()
PackageData:AwaitWearAssetRequest()
if thisFadeCount == GlobalFadeCount then
if ProfileImageThumbnailLoader then ProfileImageThumbnailLoader:Cancel() end
ProfileImageThumbnailLoader = ThumbnailLoader:Create(dummyImage, newImageUrl, ThumbnailLoader.Sizes.Large, ThumbnailLoader.AssetType.Avatar, true)
local loadResult = ProfileImageThumbnailLoader:LoadAsync(false, false)
ProfileImageThumbnailLoader = nil
end
end
if LoaderSpinner then LoaderSpinner:Cleanup() end
local newLoaderSpinner = LoadingWidget({Parent = frontImage, ZIndex = 3}, {waitForWearReady})
LoaderSpinner = newLoaderSpinner
newLoaderSpinner:AwaitFinished()
newLoaderSpinner:Cleanup()
if thisFadeCount == GlobalFadeCount then
local noPreviousImage = (frontImage.Image == "" and fadeImage.Image == "")
if noPreviousImage then
fadeoutDuration = 0
else
fadeImage.Image = frontImage.Image
end
Utility.PropertyTweener(fadeImage, 'ImageTransparency', noPreviousImage and 0 or frontImage.ImageTransparency, 1, fadeoutDuration, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(CharacterGlowBase, 'ImageTransparency', CharacterGlowBase.ImageTransparency, 1, fadeoutDuration, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(CharacterGlowTop, 'ImageTransparency', CharacterGlowTop.ImageTransparency, 1, fadeoutDuration, Utility.EaseInOutQuad, true)
frontImage.ImageTransparency = 1
frontImage.Image = dummyImage.Image
Utility.PropertyTweener(frontImage, 'ImageTransparency', frontImage.ImageTransparency, 0, duration, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(CharacterGlowBase, 'ImageTransparency', CharacterGlowBase.ImageTransparency, GLOW_TRANSPARENCY, duration, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(CharacterGlowTop, 'ImageTransparency', CharacterGlowTop.ImageTransparency, GLOW_TRANSPARENCY, duration, Utility.EaseInOutQuad, true)
end
end)
end
local function UpdateProfileImage(forceRefresh)
MyNameLabel.Text = UserData:GetDisplayName()
if forceRefresh or
LastWearingPackageAssetId ~= PackageData:GetCachedWearingPackage() or
LastWearingOutfitId ~= OutfitData:GetCachedWearingOutfitId() then
CrossfadeAvatarImage(ProfileImage, CrossfadeProfileImage, UserData.GetLocalUserIdAsync())
LastWearingPackageAssetId = PackageData:GetCachedWearingPackage()
LastWearingOutfitId = OutfitData:GetCachedWearingOutfitId()
end
end
UpdateProfileImage(true)
local function onOwnershipChanged(tile, nowOwns)
-- Move purchased packages into my outfits
if nowOwns then
local guiObject = tile and tile:GetGuiObject()
if guiObject and AvatarScroller:ContainsItem(guiObject) then
AvatarScroller:RemoveItem(guiObject)
OutfitsScroller:AddItem(guiObject)
OnAddToOutfitsScroller()
if inFocus then
GuiService.SelectedCoreObject = guiObject
end
end
end
end
local ownershipChangedCns = {}
local function listenToOwnershipChanged(tile)
local packageInfo = tile and tile:GetPackageInfo()
if packageInfo and not packageInfo:IsOwned() and (packageInfo.OwnershipChanged ~= nil) then
if not ownershipChangedCns[tile] then
ownershipChangedCns[tile] = packageInfo.OwnershipChanged:connect(function(nowOwns)
onOwnershipChanged(tile, nowOwns)
end)
end
end
end
local function removeListenToOwnershipChanged(tile)
if ownershipChangedCns[tile] then
ownershipChangedCns[tile]:disconnect()
ownershipChangedCns[tile] = nil
end
end
local packagesLoaded = false
local outfitsLoaded = true -- NOTE: we don't load outfits atm
local LoadAvatarWebDataLoader = nil
local function LoadAvatarWebData()
local function loadCatalogPackages()
local packages = PackageData:GetXboxCatalogPackagesAsync()
if packages and not packagesLoaded then
packagesLoaded = true
for _, packageInfo in pairs(packages) do
local avatarItemContainer = AvatarTile(packageInfo)
AvatarObjects[avatarItemContainer:GetGuiObject()] = avatarItemContainer
if packageInfo:IsOwned() then
OutfitsScroller:AddItem(avatarItemContainer:GetGuiObject())
OnAddToOutfitsScroller()
else
AvatarScroller:AddItem(avatarItemContainer:GetGuiObject())
listenToOwnershipChanged(avatarItemContainer)
end
if isShown then
avatarItemContainer:Show()
end
end
end
end
local function loadOutfits()
local outfits = OutfitData:GetMyOutfitsAsync()
if outfits and not outfitsLoaded then
outfitsLoaded = true
for _, outfitInfo in pairs(outfits) do
local outfitItemContainer = OutfitTile(outfitInfo)
AvatarObjects[outfitItemContainer:GetGuiObject()] = outfitItemContainer
OutfitsScroller:AddItem(outfitItemContainer:GetGuiObject())
OnAddToOutfitsScroller()
if isShown then
outfitItemContainer:Show()
end
end
end
end
if LoadAvatarWebDataLoader then return end
SelectableAvatarsContainer.Visible = false
local containerSize = SelectableAvatarsContainer.Size
LoadAvatarWebDataLoader = LoadingWidget(
{Parent = MainContainer, Position = SelectableAvatarsContainer.Position + UDim2.new(containerSize.X.Scale / 2, containerSize.X.Offset / 2, containerSize.Y.Scale / 2, containerSize.Y.Offset / 2)},
-- {loadCatalogPackages, loadOutfits})
{loadCatalogPackages})
spawn(function()
NoCatalogStatusMessage.Visible = false
LoadAvatarWebDataLoader:AwaitFinished()
LoadAvatarWebDataLoader:Cleanup()
LoadAvatarWebDataLoader = nil
SelectableAvatarsContainer.Visible = true
-- SortOutfitsScroller()
if not (packagesLoaded and outfitsLoaded) then
NoCatalogStatusMessage.Visible = true
end
if inFocus and isShown and GuiService.SelectedCoreObject == nil then
GuiService.SelectedCoreObject = this:GetDefaultSelectableObject()
end
if this.TransitionTweens == nil or #this.TransitionTweens == 0 then
this.TransitionTweens = ScreenManager:FadeInSitu(SelectableAvatarsContainer)
end
end)
end
LoadAvatarWebData()
local function onEquipChanged()
local selectedObject = GuiService.SelectedCoreObject
if AvatarObjects[selectedObject] then
local packageInfo = AvatarObjects[selectedObject]:GetPackageInfo()
if packageInfo then
UpdateEquipButton(packageInfo:IsOwned(), packageInfo:IsWearing())
end
end
SortOutfitsScroller()
end
local lastSelectedObject = GuiService.SelectedCoreObject
local function OnSelectedCoreObjectChanged()
local selectedObject = GuiService.SelectedCoreObject
if AvatarObjects[lastSelectedObject] then
AvatarObjects[lastSelectedObject]:RemoveFocus()
end
if AvatarObjects[selectedObject] then
AvatarObjects[selectedObject]:Focus()
onEquipChanged()
end
lastSelectedObject = selectedObject
end
function this:GetDefaultSelectableObject()
if OutfitsScroller.GridItems[1] then
return OutfitsScroller.GridItems[1]
end
if AvatarScroller.GridItems[1] then
return AvatarScroller.GridItems[1]
end
end
--[[ Public API ]]--
function this:GetName()
return Strings:LocalizedString('AvatarWord')
end
function this:IsFocused()
return inFocus
end
local debounceSelect = false
function this:OnSelectAction()
if debounceSelect then return end
debounceSelect = true
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and AvatarObjects[selectedObject] then
if AvatarObjects[selectedObject]:Select() then
SoundManager:Play('ButtonPress')
end
end
debounceSelect = false
end
function this:OpenEquippedPackage()
if isShown and inFocus then
for _, avatarItemContainer in pairs(AvatarObjects) do
local packageInfo = avatarItemContainer:GetPackageInfo()
if packageInfo then
if packageInfo:IsWearing() then
avatarItemContainer:OnClick()
end
end
end
end
end
--[[ End Public API ]]--
local profileImageChangeCn = nil
function this:Show()
isShown = true
Utility.DisconnectEvent(profileImageChangeCn)
profileImageChangeCn = ProfileImageContainer.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
onSizeChanged()
end
end)
onSizeChanged()
local lastUpdate = tick()
RunService:BindToRenderStep("UpdateAvatarGlow", Enum.RenderPriority.Camera.Value,
function()
local now = tick()
local delta = now - lastUpdate
CharacterGlowBase.Rotation = CharacterGlowBase.Rotation + delta * GLOW_BASE_RPM * 6 -- 6 = 360 / 60
CharacterGlowTop.Rotation = CharacterGlowTop.Rotation + delta * GLOW_TOP_RPM * 6
lastUpdate = now
end)
if not (packagesLoaded and outfitsLoaded) then
LoadAvatarWebData()
end
for _, avatarItemContainer in pairs(AvatarObjects) do
avatarItemContainer:Show()
listenToOwnershipChanged(avatarItemContainer)
local packageInfo = tile and tile:GetPackageInfo()
if packageInfo then
onOwnershipChanged(avatarItemContainer, packageInfo:IsOwned())
end
end
UpdateProfileImage()
EventHub:addEventListener(EventHub.Notifications["AvatarEquipBegin"], "AvatarPane",
function(assetId)
UpdateProfileImage(true)
spawn(function()
PackageData:AwaitWearAssetRequest()
LastWearingPackageAssetId = PackageData:GetCachedWearingPackage()
end)
end)
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentPackage"], "AvatarPane",
function()
onEquipChanged()
end)
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentOutfit"], "AvatarPane",
function()
onEquipChanged()
UpdateProfileImage()
end)
local seenXButtonPressed = false
local function onSelectAvatar(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenXButtonPressed = true
elseif inputState == Enum.UserInputState.End and seenXButtonPressed then
self:OnSelectAction()
end
end
ContextActionService:BindCoreAction("AvatarPaneSelectAction", onSelectAvatar, false, Enum.KeyCode.ButtonX)
self.TransitionTweens = ScreenManager:DefaultFadeIn(MainContainer)
SortOutfitsScroller()
MainContainer.Parent = lastParent
MainContainer.Visible = true
end
function this:Hide()
isShown = false
MainContainer.Visible = false
profileImageChangeCn = Utility.DisconnectEvent(profileImageChangeCn)
RunService:UnbindFromRenderStep("UpdateAvatarGlow")
ContextActionService:UnbindCoreAction("AvatarPaneSelectAction")
for _, avatarItemContainer in pairs(AvatarObjects) do
avatarItemContainer:Hide()
removeListenToOwnershipChanged(avatarItemContainer)
end
EventHub:removeEventListener(EventHub.Notifications["AvatarEquipBegin"], "AvatarPane")
EventHub:removeEventListener(EventHub.Notifications["DonnedDifferentPackage"], "AvatarPane")
EventHub:removeEventListener(EventHub.Notifications["DonnedDifferentOutfit"], "AvatarPane")
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
-- Clean out saved selected object so when we tab back
-- we will start with the default selection
self.SavedSelectObject = nil
end
function this:Focus()
inFocus = true
Utility.DisconnectEvent(OnGuiServiceChangedConn)
OnGuiServiceChangedConn = GuiService.Changed:connect(function(prop)
if prop == 'SelectedCoreObject' then
OnSelectedCoreObjectChanged()
end
end)
OnSelectedCoreObjectChanged()
if self.SavedSelectObject and self.SavedSelectObject:IsDescendantOf(MainContainer) then
GuiService.SelectedCoreObject = self.SavedSelectObject
else
local defaultSelection = self:GetDefaultSelectableObject()
if defaultSelection then
GuiService.SelectedCoreObject = defaultSelection
end
end
end
function this:RemoveFocus()
inFocus = false
OnGuiServiceChangedConn = Utility.DisconnectEvent(OnGuiServiceChangedConn)
local selectedObject = GuiService.SelectedCoreObject
if isShown and selectedObject and selectedObject:IsDescendantOf(MainContainer) then
self.SavedSelectObject = GuiService.SelectedCoreObject
GuiService.SelectedCoreObject = nil
end
end
function this:SetPosition(newPosition)
MainContainer.Position = newPosition
end
function this:SetParent(newParent)
lastParent = newParent
MainContainer.Parent = newParent
end
return this
end
return CreateAvatarPane
@@ -0,0 +1,215 @@
--[[
// AvatarTile.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local TextService = game:GetService('TextService')
local ContextActionService = game:GetService("ContextActionService")
local CoreGui = game:GetService("CoreGui")
local ContentProvider = game:GetService("ContentProvider")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local Http = require(Modules:FindFirstChild('Http'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Errors = require(Modules:FindFirstChild('Errors'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local PopupText = require(Modules:FindFirstChild('PopupText'))
local PurchasePackagePrompt = require(Modules:FindFirstChild('PurchasePackagePrompt'))
local BaseTile = require(Modules:FindFirstChild('BaseTile'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local ACTIVE_AVATAR_BACKGROUND_COLOR = Color3.new(45/255, 96/255, 128/255)
local INACTIVE_AVATAR_BACKGROUND_COLOR = Color3.new(106/255, 120/255, 129/255)
local function createAvatarInfoContainer(packageInfo)
local this = BaseTile()
local focused = false
local packageName = packageInfo:GetFullName()
local function wearPackageAsync()
if packageInfo:IsOwned() and not packageInfo:IsWearing() then
local result = packageInfo:WearAsync()
if result and result['success'] == true then
this:UpdateEquipButton()
else
local err = Errors.PackageEquip['Default']
ScreenManager:OpenScreen(ErrorOverlayModule(err), false)
end
end
end
local function buyPackageAsync()
local newPurchasePrompt = PurchasePackagePrompt(packageInfo)
newPurchasePrompt:SetParent(GuiRoot)
newPurchasePrompt:FadeInBackground()
ScreenManager:OpenScreen(newPurchasePrompt, false)
spawn(function()
local didPurchase = newPurchasePrompt:ResultAsync()
-- print("Buy Package Result:" , didPurchase , "ownsAsset:" , packageInfo:IsOwned())
if didPurchase then
SoundManager:Play('PurchaseSuccess')
wearPackageAsync()
end
end)
end
local PriceText = Utility.Create'TextLabel'
{
Name = 'PriceText';
Text = '';
Size = UDim2.new(1,0,0,36);
Position = UDim2.new(0,0,0,0);
TextColor3 = GlobalSettings.BlackTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.DescriptionSize;
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.PriceLabelColor;
ZIndex = 2;
Visible = false;
Parent = this.AvatarItemContainer;
};
function this:UpdatePriceText()
local newText = ""
local price = packageInfo:GetRobuxPrice()
if price == 0 then
newText = Strings:LocalizedString('FreeWord'):upper()
elseif price then
newText = "R$ " .. Utility.FormatNumberString(price)
end
PriceText.Text = newText
local priceTextSize = TextService:GetTextSize(PriceText.Text, Utility.ConvertFontSizeEnumToInt(PriceText.FontSize), PriceText.Font, Vector2.new())
PriceText.Size = UDim2.new(0,priceTextSize.X + 28,0,36)
Utility.CalculateAnchor(PriceText, UDim2.new(1,-6, 0, 6), Utility.Enum.Anchor.TopRight)
PriceText.Visible = price ~= nil and not packageInfo:IsOwned()
end
if packageInfo:GetAssetId() then
this:SetImage(Http.GetThumbnailUrlForAsset(packageInfo:GetAssetId()))
else
--TODO: show a no package image?
end
this:ColorizeImage(packageInfo:IsOwned() and 1 or 0, 0)
this:SetPopupText(packageInfo:GetName())
function this:GetAssetId()
return packageInfo:GetAssetId()
end
function this:GetPackageInfo()
return packageInfo
end
function this:UpdateOwnership()
local ownsAsset = packageInfo:IsOwned()
self:SetActive(ownsAsset)
self:ColorizeImage(ownsAsset and 1 or 0, 0)
self:UpdateEquipButton()
self:UpdatePriceText()
end
function this:UpdateEquipButton()
self.EquippedCheckmark.Visible = packageInfo:IsWearing()
end
local selectDebounce = false
function this:Select()
if selectDebounce then return false end
local result = false
if packageInfo:IsOwned() and not packageInfo:IsWearing() then
selectDebounce = true
spawn(function()
wearPackageAsync()
selectDebounce = false
end)
result = true
end
return result
end
function this:OnClick()
buyPackageAsync()
end
local isWearingConn = nil
local ownershipChangedCn = nil
local baseShow = this.Show
function this:Show()
baseShow(self)
Utility.DisconnectEvent(isWearingConn)
packageInfo.IsWearingChanged:connect(function() self:UpdateEquipButton() end)
Utility.DisconnectEvent(ownershipChangedCn)
ownershipChangedCn = packageInfo.OwnershipChanged:connect(function()
self:UpdateOwnership()
end)
self:UpdateEquipButton()
self:UpdateOwnership()
end
local baseHide = this.Hide
function this:Hide()
baseHide(self)
isWearingConn = Utility.DisconnectEvent(isWearingConn)
ownershipChangedCn = Utility.DisconnectEvent(ownershipChangedCn)
end
local baseFocus = this.Focus
local avatarItemClickConn = nil
function this:Focus()
baseFocus(self)
focused = true
Utility.DisconnectEvent(avatarItemClickConn)
avatarItemClickConn = self.AvatarItemContainer.MouseButton1Click:connect(function()
self:OnClick()
end)
self:UpdateEquipButton()
spawn(function()
wait(0.17)
if focused then
if not packageInfo:IsOwned() then
self:ColorizeImage(1)
end
end
end)
end
local baseRemoveFocus = this.RemoveFocus
function this:RemoveFocus()
baseRemoveFocus(self)
focused = false
avatarItemClickConn = Utility.DisconnectEvent(avatarItemClickConn)
-- Decolorize unowned packages
if not packageInfo:IsOwned() then
self:ColorizeImage(0)
end
end
return this
end
return createAvatarInfoContainer
@@ -0,0 +1,136 @@
--[[
// BadgeOverlay.lua
// Displays information for a single badge
// Used by GameDetail and BadgeScreen
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScrollingTextBox = require(Modules:FindFirstChild('ScrollingTextBox'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local createBadgeOverlay = function(badgeData)
local this = BaseOverlay()
local hasBadge = badgeData["IsOwned"]
this:SetImageBackgroundTransparency(0)
this:SetImageBackgroundColor(hasBadge and GlobalSettings.BadgeOwnedColor or GlobalSettings.BadgeOverlayColor)
this:SetDropShadow()
local badgeImage = Utility.Create'ImageLabel'
{
Name = "BadgeImage";
Size = UDim2.new(0, 394, 0, 394);
BackgroundTransparency = 1;
Image = 'http://www.watrbx.wtf/Thumbs/Asset.ashx?width='..
tostring(250)..'&height='..tostring(250)..'&assetId='..tostring(badgeData.AssetId);
ZIndex = this.BaseZIndex + 1;
Parent = badgeImageContainer;
}
badgeImage.Position = UDim2.new(0.5, -197, 0.5, -197)
this:SetImage(badgeImage)
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, this.RightAlign, 0, 88);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = badgeData.Name;
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
--[[ Has Badge ]]--
local hasBadgeContainer = nil
if hasBadge then
hasBadgeContainer = Utility.Create'Frame'
{
Name = "HasBadgeContainer";
Position = UDim2.new(0, titleText.Position.X.Offset, 0, titleText.Position.Y.Offset + 34);
BackgroundTransparency = 1;
Parent = this.Container;
}
local hasBadgeImage = Utility.Create'ImageLabel'
{
Name = "HasBadgeImage";
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex;
Parent = hasBadgeContainer;
}
AssetManager.LocalImage(hasBadgeImage,
'rbxasset://textures/ui/Shell/Icons/Checkmark', {['720'] = UDim2.new(0,23,0,23); ['1080'] = UDim2.new(0,35,0,35);})
hasBadgeContainer.Size = UDim2.new(0, 200, 0, hasBadgeImage.Size.Y.Offset)
local hasBadgeText = Utility.Create'TextLabel'
{
Name = "HasBadgeText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, hasBadgeImage.Size.X.Offset + 12, 0.5, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.ItalicFont;
FontSize = GlobalSettings.DescriptionSize;
TextColor3 = GlobalSettings.GreenTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Text = Strings:LocalizedString("HaveBadgeWord");
ZIndex = this.BaseZIndex;
Parent = hasBadgeContainer;
}
end
--[[ Description ]]--
local descriptionYOffset = hasBadgeContainer and hasBadgeContainer.Position.Y.Offset + hasBadgeContainer.Size.Y.Offset + 10 or
titleText.Position.Y.Offset + 40
local descriptionScrollingTextBox = ScrollingTextBox(UDim2.new(0, 762, 0, 304),
UDim2.new(0, titleText.Position.X.Offset, 0, descriptionYOffset),
this.Container)
descriptionScrollingTextBox:SetText(badgeData.Description)
descriptionScrollingTextBox:SetFontSize(GlobalSettings.TitleSize)
descriptionScrollingTextBox:SetZIndex(this.BaseZIndex)
local descriptionFrame = descriptionScrollingTextBox:GetContainer()
local okButton = Utility.Create'TextButton'
{
Name = "OkButton";
Size = UDim2.new(0, 320, 0, 66);
Position = UDim2.new(0, titleText.Position.X.Offset, 1, -66 - 55);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(Strings:LocalizedString("OkWord"));
ZIndex = this.BaseZIndex;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
--[[ Input Events ]]--
okButton.MouseButton1Click:connect(function()
this:Close()
end)
local baseFocus = this.Focus
function this:Focus()
baseFocus(this)
GuiService.SelectedCoreObject = okButton
end
return this
end
return createBadgeOverlay
@@ -0,0 +1,123 @@
--[[
// BadgeScreen.lua
// Displays a 2xN grid of badges for a game
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BadgeOverlayModule = require(Modules:FindFirstChild('BadgeOverlay'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local ScrollingGrid = require(Modules:FindFirstChild('ScrollingGrid'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local PopupText = require(Modules:FindFirstChild('PopupText'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local BaseScreen = require(Modules:FindFirstChild('BaseScreen'))
local createBadgeScreen = function(badgeData)
local this = BaseScreen()
local ROWS = 2
-- columns is dynamic
local BadgeContainer = this.Container
this:SetTitle(string.upper(Strings:LocalizedString("GameBadgesTitle")))
local defaultSelection = nil
-- create grid
local BadgeScrollGrid = ScrollingGrid()
BadgeScrollGrid:SetPosition(UDim2.new(0, 0, 0.5 - (0.57 / 2), 0))
BadgeScrollGrid:SetSize(UDim2.new(1, 0, 0, 570))
BadgeScrollGrid:SetScrollDirection(BadgeScrollGrid.Enum.ScrollDirection.Horizontal)
BadgeScrollGrid:SetParent(BadgeContainer)
BadgeScrollGrid:SetClipping(false)
BadgeScrollGrid:SetCellSize(Vector2.new(276, 276))
BadgeScrollGrid:SetSpacing(Vector2.new(18, 18))
local checkmarkImage = Utility.Create'ImageLabel'
{
Name = "CheckMarkImage";
BackgroundTransparency = 1;
ZIndex = 2;
}
AssetManager.LocalImage(checkmarkImage, 'rbxasset://textures/ui/Shell/Icons/Checkmark',
{['720'] = UDim2.new(0,23,0,23); ['1080'] = UDim2.new(0,35,0,35);})
local function connectImageInput(image, data)
image.MouseButton1Click:connect(function()
-- Do not play sound because we are opening a screen here
ScreenManager:OpenScreen(BadgeOverlayModule(data), false)
end)
end
local baseItem = Utility.Create'TextButton'
{
Name = "BadgeImage";
BorderSizePixel = 0;
BackgroundTransparency = 0.2;
BackgroundColor3 = Color3.new(64/255, 81/255, 93/255);
Text = "";
SoundManager:CreateSound('MoveSelection');
ZIndex = 2;
AssetManager.CreateShadow(1);
}
local badgeIcon = Utility.Create'ImageLabel'
{
Name = "Thumb";
Size = UDim2.new(0, 228, 0, 228);
Position = UDim2.new(0.5, -228/2, 0.5, -228/2);
BackgroundTransparency = 1;
Image = "";
ZIndex = 2;
Parent = baseItem;
}
for i = 1, #badgeData do
local data = badgeData[i]
local item = baseItem:Clone()
local thumb = item:FindFirstChild("Thumb")
if thumb then
local thumbLoader = ThumbnailLoader:Create(thumb, data.AssetId,
ThumbnailLoader.Sizes.Medium, ThumbnailLoader.AssetType.Icon)
spawn(function()
thumbLoader:LoadAsync()
end)
end
local hasBadge = data["IsOwned"]
if hasBadge then
item.BackgroundColor3 = GlobalSettings.BadgeOwnedColor;
item.BackgroundTransparency = 0;
--
local check = checkmarkImage:Clone()
check.Position = UDim2.new(1, -check.Size.X.Offset - 8, 0, 8)
check.Parent = item
end
item.Name = tostring(i)
BadgeScrollGrid:AddItem(item)
connectImageInput(item, data)
PopupText(item, data["Name"])
if not defaultSelection then
defaultSelection = item
end
end
--[[ Public API ]]--
--Override
function this:GetDefaultSelectionObject()
return defaultSelection
end
return this
end
return createBadgeScreen
@@ -0,0 +1,180 @@
--[[
// BadgeSort.lua
// Creates a badge sort for a game
// Handles the following for badges
// Displays 2x2 of badges on game details page
// Displays individual information about each badge (overlay)
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BadgeScreeenModule = require(Modules:FindFirstChild('BadgeScreen'))
local BadgeOverlayModule = require(Modules:FindFirstChild('BadgeOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local PopupText = require(Modules:FindFirstChild('PopupText'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local CreateBadgeSort = function(placeName, size, position, parent)
local this = {}
local badgeData = nil
local margin = 14
local imageSize = (size.Y.Offset - margin) / 2
local gridImages = {}
local GRID_SIZE = 4
--[[ Game Details Grid ]]--
local container = Utility.Create'Frame'
{
Name = "ImageContainer";
Size = size;
Position = position;
BackgroundTransparency = 1;
Parent = parent;
}
-- create 2x2 preview grid
local index = 1
for i = 1, GRID_SIZE/2 do
for j = 1, GRID_SIZE/2 do
local image = Utility.Create'TextButton'
{
Name = tostring(index);
Size = UDim2.new(0, imageSize, 0, imageSize);
Position = UDim2.new(0, (i - 1) * imageSize + (i - 1) * margin, 0, (j - 1) * imageSize + (j - 1) * margin);
BackgroundTransparency = GlobalSettings.FriendStatusTextTransparency;
BackgroundColor3 = GlobalSettings.BadgeFrameColor;
BorderSizePixel = 0;
ZIndex = 2;
Text = "";
ClipsDescendants = true;
Parent = container;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1);
}
local thumb = Utility.Create'ImageLabel'
{
Name = "Thumb";
Size = UDim2.new(0, 228, 0, 228);
Position = UDim2.new(0.5, -228/2, 0.5, -228/2);
BackgroundTransparency = 1;
Image = "";
ZIndex = 2;
Parent = image;
}
gridImages[index] = image
index = index + 1
end
end
-- more button visible when #badges > 4
local moreBadgesButton = Utility.Create'ImageButton'
{
Name = "MoreBadgesButton";
BackgroundTransparency = 1;
Visible = false;
Parent = container;
SoundManager:CreateSound('MoveSelection');
ZIndex = 2;
}
AssetManager.LocalImage(moreBadgesButton, 'rbxasset://textures/ui/Shell/Buttons/MoreButton',
{['720'] = UDim2.new(0,72,0,33); ['1080'] = UDim2.new(0,108,0,50);})
moreBadgesButton.Position = UDim2.new(1, - moreBadgesButton.AbsoluteSize.x, 1, 12)
local function updateMoreButton(isSelected)
local uri = isSelected and 'rbxasset://textures/ui/Shell/Buttons/MoreButtonSelected'
or 'rbxasset://textures/ui/Shell/Buttons/MoreButton'
AssetManager.LocalImage(moreBadgesButton, uri, {['720'] = UDim2.new(0,72,0,33); ['1080'] = UDim2.new(0,108,0,50);})
end
moreBadgesButton.SelectionGained:connect(function()
updateMoreButton(true)
end)
moreBadgesButton.SelectionLost:connect(function()
updateMoreButton(false)
end)
local checkmarkImage = Utility.Create'ImageLabel'
{
Name = "CheckMarkImage";
BackgroundTransparency = 1;
ZIndex = 2;
}
AssetManager.LocalImage(checkmarkImage, 'rbxasset://textures/ui/Shell/Icons/Checkmark',
{['720'] = UDim2.new(0,23,0,23); ['1080'] = UDim2.new(0,35,0,35);})
local function setBadgeData()
if not badgeData then
print("BadgeSort: failed to set badge data because data is nil.")
return
end
--
for i = 1, #gridImages do
if badgeData[i] then
local data = badgeData[i]
local thumb = gridImages[i]:FindFirstChild("Thumb")
if thumb then
local thumbLoader = ThumbnailLoader:Create(thumb, data.AssetId,
ThumbnailLoader.Sizes.Medium, ThumbnailLoader.AssetType.Icon)
spawn(function()
thumbLoader:LoadAsync()
end)
end
local hasBadge = data["IsOwned"]
if hasBadge then
gridImages[i].BackgroundColor3 = GlobalSettings.BadgeOwnedColor
gridImages[i].BackgroundTransparency = 0
--
local check = checkmarkImage:Clone()
check.Position = UDim2.new(1, -check.Size.X.Offset - 8, 0, 8)
check.Parent = gridImages[i]
end
--
gridImages[i].MouseButton1Click:connect(function()
ScreenManager:OpenScreen(BadgeOverlayModule(data), false)
end)
-- connect popup text
PopupText(gridImages[i], data["Name"])
end
end
end
--[[ Input Events ]]--
moreBadgesButton.MouseButton1Click:connect(function()
EventHub:dispatchEvent(EventHub.Notifications["OpenBadgeScreen"], badgeData, placeName)
end)
--[[ Public API ]]--
function this:GetContainer()
return container
end
function this:Initialize(data)
if not badgeData then
badgeData = data
setBadgeData()
if #data > GRID_SIZE then
moreBadgesButton.Visible = true
end
end
end
function this:Destroy()
container:Destroy()
gridImages = nil
end
return this
end
return CreateBadgeSort
@@ -0,0 +1,145 @@
--[[
// BaseOverlay.lua
// Implements a base overlay for overlay screens.
// Any other overlay classes should require this module
// first, then implement its own logic
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScrollingTextBox = require(Modules:FindFirstChild('ScrollingTextBox'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local FADE_TIME = 0.25
local createBaseOverlay = function()
local this = {}
local OVERLAY_TRANSPARENCY = GlobalSettings.ModalBackgroundTransparency
this.RightAlign = 776
this.BaseZIndex = 8
local modalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BackgroundColor3 = Color3.new();
BorderSizePixel = 0;
ZIndex = this.BaseZIndex;
}
local container = Utility.Create'Frame'
{
Name = "Container";
Size = UDim2.new(1, 0, 0, 668);
Position = UDim2.new(0, 0, 0, 227);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.OverlayColor;
ZIndex = this.BaseZIndex;
Parent = modalOverlay;
}
local imageContainer = Utility.Create'Frame'
{
Name = "ImageContainer";
Size = UDim2.new(0, 576, 0, 642);
Position = UDim2.new(0, 100, 0.5, -321);
BorderSizePixel = 0;
BackgroundTransparency = 1;
BackgroundColor3 = Color3.new();
ZIndex = this.BaseZIndex + 1;
Parent = container;
}
this.Container = container
--[[ Public API ]]--
function this:SetImageBackgroundTransparency(value)
imageContainer.BackgroundTransparency = value
end
function this:SetImageBackgroundColor(value)
imageContainer.BackgroundColor3 = value
end
function this:SetImage(guiImage)
guiImage.Position = UDim2.new(0.5, -guiImage.Size.X.Offset/2, 0.5, -guiImage.Size.Y.Offset/2)
guiImage.Parent = imageContainer
end
function this:GetOverlaySound()
return 'OverlayOpen'
end
function this:SetDropShadow()
local dropShadow = AssetManager.CreateShadow(3)
dropShadow.Parent = imageContainer
end
function this:GetPriority()
return GlobalSettings.DefaultPriority
end
function this:Show()
modalOverlay.Parent = ScreenManager:GetScreenGuiByPriority(self:GetPriority())
local overlayTweenIn = Utility.PropertyTweener(modalOverlay, "BackgroundTransparency",
1, OVERLAY_TRANSPARENCY, FADE_TIME, Utility.EaseInOutQuad, nil)
SoundManager:Play(self:GetOverlaySound())
-- Show the modalOverlay when we are shown
modalOverlay.Visible = true
end
function this:Hide()
local overlayTweenOut = Utility.PropertyTweener(modalOverlay, "BackgroundTransparency",
OVERLAY_TRANSPARENCY, 1, FADE_TIME, Utility.EaseInOutQuad, true,
function()
modalOverlay:Destroy()
end)
container.Parent = nil
container:Destroy()
end
function this:Focus()
ContextActionService:BindCoreAction("CloseOverlay",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
end
end,
false, Enum.KeyCode.ButtonB)
GuiService:AddSelectionParent("Overlay", container)
-- Don't show overlays when not focused
modalOverlay.Visible = true
end
function this:RemoveFocus()
ContextActionService:UnbindCoreAction("CloseOverlay")
GuiService:RemoveSelectionGroup("Overlay")
-- Don't show overlays when not focused
modalOverlay.Visible = false
end
function this:Close()
if ScreenManager:GetTopScreen() == self then
SoundManager:Play('ButtonPress')
ScreenManager:CloseCurrent()
return true
end
return false
end
return this
end
return createBaseOverlay
@@ -0,0 +1,143 @@
--[[
// BaseScreen.lua
// Creates a base screen with breadcrumbs and title. Do not use for a pane/tab
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function createBaseScreen()
local this = {}
local defaultSelectionObject = nil
local lastParent = nil
local container = Utility.Create'Frame'
{
Name = "Container";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
}
local backImage = Utility.Create'ImageLabel'
{
Name = "BackImage";
BackgroundTransparency = 1;
Parent = container;
}
AssetManager.LocalImage(backImage,
'rbxasset://textures/ui/Shell/Icons/BackIcon', {['720'] = UDim2.new(0,32,0,32); ['1080'] = UDim2.new(0,48,0,48);})
local backText = Utility.Create'TextLabel'
{
Name = "BackText";
Size = UDim2.new(0, 0, 0, backImage.Size.Y.Offset);
Position = UDim2.new(0, backImage.Size.X.Offset + 8, 0, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = "";
Parent = container
}
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 35);
Position = UDim2.new(0, 16, 0, backImage.Size.Y.Offset + 74);
BackgroundTransparency = 1;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = "";
Parent = container;
}
--[[ Public API ]]--
this.Container = container
function this:SetTitle(newTitle)
titleText.Text = newTitle
end
function this:SetBackText(newText)
backText.Text = newText
end
function this:GetDefaultSelectionObject()
return defaultSelectionObject
end
function this:Destroy()
self.Container:Destroy()
self = nil
end
--[[ Public API - Screen Management ]]--
function this:SetPosition(newPosition)
self.Container.Position = newPosition
end
function this:SetParent(newParent)
lastParent = newParent
self.Container.Parent = newParent
end
function this:GetName()
return titleText.Text
end
function this:Show()
local prevScreen = ScreenManager:GetScreenBelow(self)
if prevScreen and prevScreen.GetName then
self:SetBackText(prevScreen:GetName())
else
self:SetBackText(string.upper(Strings:LocalizedString("BackWord")))
end
self.Container.Parent = lastParent
self.TransisitionTweens = ScreenManager:DefaultFadeIn(self.Container)
ScreenManager:PlayDefaultOpenSound()
end
function this:Hide()
self.Container.Parent = nil
ScreenManager:DefaultCancelFade(self.TransisitionTweens)
self.TransisitionTweens = nil
end
function this:Focus()
if self.SavedSelectedObject and self.SavedSelectedObject:IsDescendantOf(self.Container) then
GuiService.SelectedCoreObject = self.SavedSelectedObject
else
GuiService.SelectedCoreObject = self:GetDefaultSelectionObject()
end
ContextActionService:BindCoreAction("ReturnFromScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
self:Destroy()
end
end,
false, Enum.KeyCode.ButtonB)
end
function this:RemoveFocus()
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(self.Container) then
self.SavedSelectedObject = selectedObject
GuiService.SelectedCoreObject = nil
end
ContextActionService:UnbindCoreAction("ReturnFromScreen")
end
return this
end
return createBaseScreen
@@ -0,0 +1,200 @@
--[[
// BaseSignInScreen.lua
// Creates a base screen to be used for account linking and sign in
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseScreen = require(Modules:FindFirstChild('BaseScreen'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local TextBox = require(Modules:FindFirstChild('TextBox'))
local Utility = require(Modules:FindFirstChild('Utility'))
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local TERMS_OF_SERVICE_URI = "https://en.help.watrbx.wtf/hc/en-us/articles/205358110"
local function createBaseAccountScreen()
local this = BaseScreen()
local DefaultButtonColor = GlobalSettings.GreyButtonColor
local SelectedButtonColor = GlobalSettings.GreySelectedButtonColor
local DefaultButtonTextColor = GlobalSettings.WhiteTextColor
local SelectedButtonTextColor = GlobalSettings.TextSelectedColor
local ScreenDivide = Utility.Create'Frame'
{
Name = "ScreenDivide";
Size = UDim2.new(0, 2, 0, 610);
Position = UDim2.new(0, 822, 0.5, -305);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.PageDivideColor;
Parent = this.Container;
}
local DescriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 740, 0, 500);
Position = UDim2.new(0, 16, 0, 334);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
TextWrapped = true;
Text = "";
Parent = this.Container;
}
local TermsOfServiceText = Utility.Create'TextLabel'
{
Name = "TermsOfServiceText";
Size = UDim2.new(0, 740, 0, 128);
Position = UDim2.new(0, 16, 0, 835);
BackgroundTransparency = 1;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
TextWrapped = true;
Text = Strings:LocalizedString("ToSInfoLinkPhrase");
Parent = this.Container;
}
local UsernameObject = TextBox(UDim2.new(0, 600, 0, 84))
UsernameObject:SetPosition(UDim2.new(0, ScreenDivide.Position.X.Offset + ScreenDivide.Size.X.Offset + 86, 0, 334))
UsernameObject:SetSpacing(Vector2.new(20, 0))
UsernameObject:SetParent(this.Container)
local UsernameTextBox = UsernameObject:GetTextBox()
local UsernameSelection = UsernameObject:GetContainer()
this.UsernameObject = UsernameObject
this.UsernameTextBox = UsernameTextBox
this.UsernameSelection = UsernameSelection
local PasswordObject = TextBox(UDim2.new(0, 600, 0, 84))
PasswordObject:SetPosition(UDim2.new(0, ScreenDivide.Position.X.Offset + ScreenDivide.Size.X.Offset + 86, 0, 484))
PasswordObject:SetSpacing(Vector2.new(20, 0))
PasswordObject:SetParent(this.Container)
local PasswordTextBox = PasswordObject:GetTextBox()
local PasswordSelection = PasswordObject:GetContainer()
this.PasswordObject = PasswordObject
this.PasswordTextBox = PasswordTextBox
this.PasswordSelection = PasswordSelection
local function CreateSlicedTextButton(name, text, position)
name = name or ""
local newButton = Utility.Create'ImageButton'
{
Name = name .. "Button";
Size = UDim2.new(0, 320, 0, 64);
Position = position or UDim2.new();
BackgroundTransparency = 1;
ImageColor3 = DefaultButtonColor;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
ZIndex = 2;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1)
}
local newText = Utility.Create'TextLabel'
{
Name = name .. "Text";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = DefaultButtonTextColor;
Text = text;
ZIndex = 2;
Parent = newButton;
}
newButton.SelectionGained:connect(function()
newButton.ImageColor3 = SelectedButtonColor
newText.TextColor3 = SelectedButtonTextColor
end)
newButton.SelectionLost:connect(function()
newButton.ImageColor3 = DefaultButtonColor
newText.TextColor3 = DefaultButtonTextColor
end)
return newButton, newText
end
local function TryLaunchUri(uri)
local success, msg = pcall(function()
assert(not UserSettings().GameSettings:InStudioMode(), "Can't use in studio")
PlatformService:LaunchPlatformUri(uri)
end)
if not success then
print(string.format("PlatformService:LaunchPlatformUri failed to launch uri: %s, for reason: %s", uri, msg))
end
end
local SignInButton, SignInText = CreateSlicedTextButton("SignIn",
string.upper(Strings:LocalizedString("SignInPhrase")),
UDim2.new(0, PasswordSelection.Position.X.Offset, 0,
PasswordSelection.Position.Y.Offset + PasswordSelection.Size.Y.Offset + 66))
this.SignInButton = SignInButton
local tosButtonY = SignInButton.Position.Y.Offset -- + SignInButton.Size.Y.Offset + 30
local ToSButton, ToSText = CreateSlicedTextButton("ToS",
string.upper(Strings:LocalizedString("ToSPhrase")),
UDim2.new(0, SignInButton.Position.X.Offset + SignInButton.Size.X.Offset + 10,
0, tosButtonY))
ToSButton.Size = UDim2.new(0, 270, 0, ToSButton.Size.Y.Offset)
local tosButtonLastPress = tick() - 1
ToSButton.MouseButton1Click:connect(function()
if tick() - tosButtonLastPress < 1 then return end
tosButtonLastPress = tick()
TryLaunchUri(TERMS_OF_SERVICE_URI)
end)
--[[
local PrivacyButton, PrivacyText = CreateSlicedTextButton("Privacy",
string.upper(Strings:LocalizedString("PrivacyPhrase")),
UDim2.new(0, ToSButton.Position.X.Offset + ToSButton.Size.X.Offset + 5,
0, tosButtonY))
PrivacyButton.MouseButton1Click:connect(function()
TryLaunchUri("http://www.watrbx.wtf/info/Privacy.aspx")
end)
--]]
-- Override selection - issue with selections remembering their last selection, so in some cases
-- the password selection become unselectable. I've talk to Ben about this and we're going to fix it
-- TODO: Remove this when selection memory is fixed
UsernameSelection.NextSelectionDown = PasswordSelection
SignInButton.NextSelectionUp = PasswordSelection
--[[ Public API ]]--
--Override
function this:GetDefaultSelectionObject()
return UsernameSelection
end
function this:SetDescriptionText(newText)
DescriptionText.Text = newText
end
function this:SetButtonText(newText)
SignInText.Text = string.upper(newText)
end
return this
end
return createBaseAccountScreen
@@ -0,0 +1,131 @@
--[[
// BaseTile.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local PopupText = require(Modules:FindFirstChild('PopupText'))
local ACTIVE_AVATAR_BACKGROUND_COLOR = Color3.new(45/255, 96/255, 128/255)
local INACTIVE_AVATAR_BACKGROUND_COLOR = Color3.new(39/255, 69/255, 82/255) --Color3.new(106/255, 120/255, 129/255)
local function createBaseTileContainer()
local this = {}
this.focused = false
this.active = false
local avatarItemContainer = Utility.Create'ImageButton'
{
Name = 'AvatarItemContainer';
Size = UDim2.new(0,220,0,220);
BorderSizePixel = 0;
BackgroundTransparency = 0;
BackgroundColor3 = this.active and ACTIVE_AVATAR_BACKGROUND_COLOR or INACTIVE_AVATAR_BACKGROUND_COLOR;
AutoButtonColor = false;
ClipsDescendants = true;
ZIndex = 2;
AssetManager.CreateShadow(1);
SoundManager:CreateSound('MoveSelection');
}
local myPopText = PopupText(avatarItemContainer, '')
myPopText:SetZIndex(3)
local avatarImage = Utility.Create'ImageLabel'
{
Name = "AvatarImage";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ZIndex = 2;
Parent = avatarItemContainer;
}
local equippedCheckmark = Utility.Create'ImageLabel'
{
Name = "EquippedCheckmark";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ZIndex = 3;
Visible = false;
Image = 'rbxasset://textures/ui/Shell/Icons/EquippedOverlay.png';
Parent = avatarItemContainer;
}
this.AvatarItemContainer = avatarItemContainer
this.AvatarImage = avatarImage
this.EquippedCheckmark = equippedCheckmark
local function colorizeImage(newColor, duration)
duration = duration or 0.2
Utility.PropertyTweener(avatarImage, 'ImageColor3', avatarImage.ImageColor3.r, newColor, duration,
function(...) local scalar = Utility.EaseOutQuad(...) return Color3.new(scalar, scalar, scalar) end, true)
end
function this:UpdateEquipButton()
end
function this:ColorizeImage(...)
colorizeImage(...)
end
function this:SetPopupText(newText)
myPopText:SetText(newText)
end
function this:SetImage(imgUrl)
avatarImage.Image = imgUrl
end
function this:GetGuiObject()
return avatarItemContainer
end
function this:GetPackageInfo()
end
function this:OnClick()
end
function this:SetActive(isActive)
self.active = isActive
avatarItemContainer.BackgroundColor3 = self.active and ACTIVE_AVATAR_BACKGROUND_COLOR or INACTIVE_AVATAR_BACKGROUND_COLOR;
end
function this:Select()
end
function this:Show()
end
function this:Hide()
end
function this:Focus()
self.focused = true
end
function this:RemoveFocus()
self.focused = false
end
return this
end
return createBaseTileContainer
@@ -0,0 +1,261 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
-- CFrame animations by Tomarty :)
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local RunService = game:GetService("RunService")
local PlatformService;
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService('UserInputService')
local function clerp(x)
return (math.sin(math.pi * (x - 0.5)) + 1) * 0.5
end
local function clerp0(x)
return clerp(x * 0.5)
end
local function clerp1(x)
return clerp(x * 0.5 + 0.5)
end
local CameraManager = {}
function CameraManager:StartTransitionScreenEffect()
if PlatformService then
PlatformService.Brightness = GlobalSettings.SceneBrightness
PlatformService.Contrast = GlobalSettings.SceneContrast
PlatformService.GrayscaleLevel = GlobalSettings.SceneGrayscaleLevel
PlatformService.TintColor = GlobalSettings.SceneTintColor
PlatformService.BlurIntensity = GlobalSettings.SceneMotionBlurIntensity
end
end
function CameraManager:EndTransitionScreenEffect( ... )
-- if PlatformService then
-- PlatformService.Brightness = GlobalSettings.SceneBrightness
-- PlatformService.Contrast = GlobalSettings.SceneContrast
-- PlatformService.GrayscaleLevel = GlobalSettings.SceneGrayscaleLevel
-- PlatformService.TintColor = GlobalSettings.SceneTintColor
-- PlatformService.BlurIntensity = GlobalSettings.SceneBlurIntensity
-- end
end
local cameraConnection;
local camera = workspace.CurrentCamera
local function onCameraChanged()
if workspace.CurrentCamera then
camera = workspace.CurrentCamera
camera.CameraType = 'Scriptable'
end
end
workspace.Changed:connect(function()
if prop == 'CurrentCamera' then
onCameraChanged()
end
end)
onCameraChanged()
local function CFrameBezierLerp(cframes, t)
local cframes2 = {}
for i = 1, #cframes - 1 do
cframes2[i] = cframes[i]:lerp(cframes[i + 1], t)
end
if #cframes2 == 1 then
return cframes2[1]
end
return CFrameBezierLerp(cframes2, t)
end
local cameraMoveCn = nil
local gamepadInput = Vector2.new(0, 0)
function CameraManager:EnableCameraControl()
cameraMoveCn = Utility.DisconnectEvent(cameraMoveCn)
cameraMoveCn = UserInputService.InputChanged:connect(function(input)
if input.KeyCode == Enum.KeyCode.Thumbstick2 then
gamepadInput = input.Position or gamepadInput
gamepadInput = Vector2.new(gamepadInput.X, gamepadInput.Y)
end
end)
end
function CameraManager:DisableCameraControl()
cameraMoveCn = Utility.DisconnectEvent(cameraMoveCn)
gamepadInput = Vector2.new(0, 0)
end
local getGamepadInputCFrame; do
local gamepadInputLerping = Vector2.new(0, 0)
local timestamp0 = tick()
function getGamepadInputCFrame()
local timestamp1 = tick()
local deltaTime = timestamp1 - timestamp0
timestamp0 = timestamp1
local unit = 0.125 ^ deltaTime
gamepadInputLerping = gamepadInputLerping * unit + gamepadInput * (1 - unit)
return CFrame.new(gamepadInputLerping.X/8, gamepadInputLerping.Y/8, 0) * CFrame.Angles(0, -gamepadInputLerping.X / 12, 0) * CFrame.Angles(gamepadInputLerping.Y / 12, 0, 0)
end
end
local function lerpToCFrame(cframes, length, useVelocity, yieldEarlyLength)
-- print("Lerpin")
local name = "CameraScriptCutsceneLerp"
yieldEarlyLength = yieldEarlyLength or 0
yieldEarlyLength = 1 - yieldEarlyLength / length
if useVelocity then
-- TODO: estimate the total distance traveled
--length = (cf0.p - cf1.p).magnitude / length
end
if cameraConnection then
cameraConnection:disconnect()
cameraConnection = nil
end
local timestamp0 = tick()
local event = Instance.new("BindableEvent")
cameraConnection = {
disconnect = function()
if event then
event:Fire()
event = nil
end
RunService:UnbindFromRenderStep(name)
end
}
RunService:BindToRenderStep(name, Enum.RenderPriority.Camera.Value, function()
local timestamp1 = tick()
local t = (timestamp1 - timestamp0) / length
if t >= 1 then
t = 1
end
--print(t)
local cam = Workspace.CurrentCamera
cam.CoordinateFrame = CFrameBezierLerp(cframes, t) * getGamepadInputCFrame()
if t >= 1 then
if event then
event:Fire()
event = nil
end
cameraConnection:disconnect()
elseif t >= yieldEarlyLength then
--local test = cf0:lerp(cf1, (t))
--print(test)
if event then
event:Fire()
event = nil
end
end
end)
event.Event:wait()
end
local function GetCameraParts(model)
local parts = {}
for i, part in pairs(model:GetChildren()) do
parts[tonumber(part.Name:sub(4, -1))] = part
part.Transparency = 1
end
return parts
end
local function PlayAnimationAsync(cameras, doTransition, isPan)
if #cameras <= 1 then
return
end
local cam = Workspace.CurrentCamera
local time = 1.7
cam.CoordinateFrame = cameras[1].CFrame
if (doTransition) then
delay(time, function()
if PlatformService then
Utility.PropertyTweener(PlatformService, 'Contrast', PlatformService.Contrast, GlobalSettings.SceneContrast, time, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(PlatformService, 'BlurIntensity', PlatformService.BlurIntensity, GlobalSettings.SceneMotionBlurIntensity, time, Utility.EaseInOutQuad, true)
end
end)
end
local cframes = {}
for i = 1, #cameras do
cframes[i] = cameras[i].CFrame * CFrame.new(0, 0, -cameras[i].Size.Z / 2)
end
lerpToCFrame(cframes, isPan and 60 or 120, false, time + 0.1)
-- lerpToCFrame(cframes, 15, false, time + 0.1)
if PlatformService then
Utility.PropertyTweener(PlatformService, 'Contrast', GlobalSettings.SceneContrast, -1, time, Utility.EaseInOutQuad, true)
Utility.PropertyTweener(PlatformService, 'BlurIntensity', GlobalSettings.SceneMotionBlurIntensity, 50, time, Utility.EaseInOutQuad, true)
end
wait(time)
end
pcall(function()
local function recurse(model)
local children = model:GetChildren()
for i = 1, #children do
local child = children[i]
if child:IsA("BasePart") then
child.Locked = true
child.Anchored = true
child.CanCollide = false
end
recurse(child)
end
end
recurse(workspace)
end)
local ZoneManager = require(script.Parent:WaitForChild("CameraManagerModules"):WaitForChild("CameraManager_ZoneManager"))
function CameraManager:CameraMoveToAsync( ... )
local cameraSets = {}
for k, f in pairs(workspace:WaitForChild("Cameras"):GetChildren()) do
cameraSets[f.Name] = GetCameraParts(f)
end
local first = true
while true do
ZoneManager:SetZone("City")
PlayAnimationAsync(cameraSets.City, not first, false)
ZoneManager:SetZone("Space")
PlayAnimationAsync(cameraSets.Space, true, true)
ZoneManager:SetZone("Volcano")
PlayAnimationAsync(cameraSets.Volcano, true, true)
first = false
end
end
return CameraManager
@@ -0,0 +1,121 @@
-- Written by Tomarty, Copyright ROBLOX 2015
local runService = game:GetService("RunService")
local ZoneAnimator;
do
local animators = {}
local function GetAnimator(zone)
if animators[zone] then
return animators[zone]
end
local moduleScript = script.Parent:WaitForChild("CameraManager_Zones"):FindFirstChild("CameraManagerZone_" .. tostring(zone))
if not moduleScript then
return
end
-- print('require', moduleScript:GetFullName())
local animator = require(moduleScript)
animators[zone] = animator
return animator
end
ZoneAnimator = {
CurrentZone = nil;
}
local connection;
function ZoneAnimator:SetZone(zone)
if ZoneAnimator.CurrentZone == zone then
return
end
ZoneAnimator.CurrentZone = zone
if connection then
connection:disconnect()
connection = nil
end
local animator = GetAnimator(zone)
if not animator then
return
end
animator:SetEnabled(true)
connection = {
disconnect = function()
animator:SetEnabled(false)
end
}
end
end
local SkyboxManager;
do
SkyboxManager = {}
local activeSkybox = nil
function SkyboxManager:SetZone(id)
if activeSkybox then
activeSkybox.Parent = nil
activeSkybox = nil
end
local Skyboxes = game:GetService("ReplicatedStorage"):WaitForChild("Skyboxes")
local skybox = Skyboxes:FindFirstChild(id or "default")
if skybox then
activeSkybox = skybox:Clone()
local lighting = game:GetService("Lighting")
skybox.Parent = lighting
pcall(function()
for k, v in pairs(skybox:GetChildren()) do
lighting[v.Name] = v.Value
end
end)
end
end
end
local ZoneManager = {
Zone = nil;
}
--local ZoneAnimator = require(script.ZoneAnimator)
--local Skybox = require(script.Skybox)
local function setZoneInternal(zone)
ZoneManager.Zone = zone
SkyboxManager:SetZone(zone)
ZoneAnimator:SetZone(zone)
end
function ZoneManager:SetZone(zone)
if zone == ZoneManager.Zone then
return
end
if runService:IsRunning() then
setZoneInternal(zone)
else
spawn(function()
while not runService:IsRunning() do wait() end
setZoneInternal(zone)
end)
end
end
return ZoneManager
@@ -0,0 +1,218 @@
local runService = game:GetService("RunService")
local Zones = workspace:FindFirstChild("Zones")
local City = Zones and Zones:FindFirstChild("City")
local CFramer = {}; do
function CFramer.GetAllParts(model, list)
list = list or {}
if model:IsA("BasePart") then
list[#list + 1] = model
end
local children = model:GetChildren()
for i = 1, #children do
CFramer.GetAllParts(children[i], list)
end
return list
end
function CFramer.GetAllPartOffsets(parts, cframeBase)
-- The offset is a part's cframe relative to cframeBase
local offsets = {}
local cframeBase_inv = cframeBase:inverse()
for i = 1, #parts do
offsets[i] = cframeBase_inv * parts[i].CFrame
end
return offsets
end
function CFramer.newCFramer(parts, offsets)
return function(cf)
for i = 1, #parts do
parts[i].CFrame = cf * offsets[i]
end
end
end
function CFramer.newCFramerFromModel(model, cframeBase)
local parts = CFramer.GetAllParts(model)
local offsets = CFramer.GetAllPartOffsets(parts, cframeBase)
return CFramer.newCFramer(parts, offsets)
end
end
local renderTrains;
spawn(function()
while not runService:IsRunning() do wait(0.1) end
if not City then
City = workspace:WaitForChild("Zones"):WaitForChild("City")
end
local exampleTrain = City.Train:Clone()
local function newTrain()
local train = exampleTrain:Clone()
train.Parent = workspace
return CFramer.newCFramerFromModel(train.Parts, train.Track.CFrame)
end
local function newTrack(part, direction)
if not direction then
direction = math.random(0, 1) * 2 - 1
end
local length = part.Size.Y
local cf = part.CFrame * CFrame.Angles(math.pi / 2 * (direction + 1), 0, 0)
local cf0 = cf * CFrame.new(0, -length/2, 0)
return function(train, time)
local distance = time * 48
if distance >= 0 and distance <= length then
train(cf0 * CFrame.new(0, distance, 0))
end
end, part.Size.Y
end
local train0 = newTrain()
local train1 = newTrain()
local train2 = newTrain()
local train3 = newTrain()
local train4 = newTrain()
local train5 = newTrain()
local train6 = newTrain()
local tracks = City.Tracks
local track0 = newTrack(tracks.TrackA1)--, 1)
local track1 = newTrack(tracks.TrackA2)--, -1)
local track2 = newTrack(tracks.TrackA3)--, 1)
local track3 = newTrack(tracks.TrackB1)--, 1)
local track4 = newTrack(tracks.TrackB2)--, -1)
local track5 = newTrack(tracks.TrackC1)--, -1)
local track6 = newTrack(tracks.TrackC2)--, 1)
local track7 = newTrack(tracks.TrackC3)--, 1)
function renderTrains(timestamp)
track0(train0, timestamp % 15)
track1(train1, (timestamp - 3) % 20)
track2(train2, (timestamp - 5.5) % 18)
track3(train3, (timestamp - 5.5) % 25)
track6(train4, (timestamp - 2.5) % 30)
end
end)
local renderFlows;
spawn(function()
if not City then
City = workspace:WaitForChild("Zones"):WaitForChild("City")
end
local function newFlow(model, flowLength)
local parts = {}
local partParts = {}
local partPartOffsets = {}
local cframes = {}
for j, part in pairs(model:GetChildren()) do
local i = tonumber(part.Name)
parts[i] = part
cframes[i] = part.CFrame
local pParts = {}
local pPartOffsets = {}
for i, pPart in pairs(part:GetChildren()) do
if pPart:IsA("BasePart") then
pParts[#pParts + 1] = pPart
pPartOffsets[#pPartOffsets + 1] = part.CFrame:inverse() * pPart.CFrame
end
end
partParts[i] = pParts
partPartOffsets[i] = pPartOffsets
end
local partCycle = 0
local unitOffset = 0
local timestamp0 = 0
return function(timestamp1)
if timestamp1 < timestamp0 then
timestamp0 = timestamp1
end
local unit = (timestamp1 - timestamp0) / flowLength + unitOffset
if unit >= 1 then
partCycle = (partCycle + math.floor(unit)) % #parts
unit = unit % 1
unitOffset = unit
timestamp0 = timestamp1 -- - deltaTime * flowLength
end
for x = 0, #parts - 2 do
local i = (x - partCycle) % #cframes + 1
local cf = cframes[x + 1]:lerp(cframes[x + 2], unit)
parts[i].CFrame = cf
local pParts = partParts[i]
local pPartOffsets = partPartOffsets[i]
for n = 1, #pParts do
pParts[n].CFrame = cf * pPartOffsets[n]
end
end
end
end
local flow1 = newFlow(City.Pipe1.Flow, 0.5)
local flow2 = newFlow(City.Pipe2.Flow, 0.65)
function renderFlows(t)
flow1(t)
flow2(t)
end
end)
local connection;
local self = {}
function self:SetEnabled(enabled)
if enabled and connection then
return
end
if connection then
connection:disconnect()
connection = nil
end
if not enabled then
return
end
local timestamp0 = tick()
connection = game:GetService("RunService").RenderStepped:connect(function()
local t = tick() - timestamp0
if renderFlows then
renderFlows(t)
end
if renderTrains then
renderTrains(t)
end
end)
end
return self
@@ -0,0 +1,145 @@
local runService = game:GetService("RunService")
local CFrameOffsetter = {};
do
function CFrameOffsetter.GetAllParts(model, list)
list = list or {}
if model:IsA("BasePart") then
list[#list + 1] = model
end
local children = model:GetChildren()
for i = 1, #children do
CFrameOffsetter.GetAllParts(children[i], list)
end
return list
end
function CFrameOffsetter.GetAllPartOffsets(parts, cframeBase)
-- The offset is a part's cframe relative to cframeBase
local offsets = {}
local cframeBase_inv = cframeBase:inverse()
for i = 1, #parts do
offsets[i] = cframeBase_inv * parts[i].CFrame
end
return offsets
end
function CFrameOffsetter.newOffsetter(parts, offsets, cframeBase)
return function(offset)
local cf = cframeBase * offset
for i = 1, #parts do
parts[i].CFrame = cf * offsets[i]
end
end
end
function CFrameOffsetter.newOffsetterFromModel(model, cframeBase)
local parts = CFrameOffsetter.GetAllParts(model)
local offsets = CFrameOffsetter.GetAllPartOffsets(parts, cframeBase)
return CFrameOffsetter.newOffsetter(parts, offsets, cframeBase)
end
end
local newOffsetterFromModel = CFrameOffsetter.newOffsetterFromModel
local renderStation;
spawn(function()
while not runService:IsRunning() do wait(0.1) end
local Space = workspace:WaitForChild("Zones"):WaitForChild("Space")
local model = Space.Station
local cframeBase = model.rings.center.CFrame
local ring1 = newOffsetterFromModel(model.rings.Ring1, cframeBase)
local ring2 = newOffsetterFromModel(model.rings.Ring2, cframeBase)
local ring3 = newOffsetterFromModel(model.rings.Ring3, cframeBase)
local station = newOffsetterFromModel(model.station, cframeBase)
function renderStation(t)
ring1(
CFrame.Angles(0, (t / 6 % (math.pi * 2)), 0)
* CFrame.new(0, math.sin(t / 4 % (math.pi * 2)) * 1, 0)
)
ring2(
CFrame.Angles(0, -(t / 4 % (math.pi * 2)), 0)
* CFrame.new(0, math.sin(2 + t / 2 % (math.pi * 2)) * 1, 0)
)
ring3(
CFrame.Angles(0, (t / 3.5 % (math.pi * 2)), 0)
* CFrame.new(0, math.sin(1 + t / 2 % (math.pi * 2)) * 1, 0)
)
station(
CFrame.Angles(0, (t / -32 % (math.pi * 2)), 0)
* CFrame.new(0, math.sin(t / 4 % (math.pi * 2)) * 3 - 2, 0)
)
end
end)
local renderOrbit;
spawn(function()
while not runService:IsRunning() do wait(0.1) end
local Space = workspace:WaitForChild("Zones"):WaitForChild("Space")
local model = Space.Orbit
local cframeBase = model.center.CFrame
local cuteMoonThings = newOffsetterFromModel(model, cframeBase)
local ringParts = {Space.Disk, Space.Disk2, Space.Disk3}
local rings = {}
for i = 1, #ringParts do
rings[i] = newOffsetterFromModel(ringParts[i], ringParts[i].CFrame)
end
function renderOrbit(t)
cuteMoonThings(
CFrame.Angles((t / 80 % (math.pi * 2)), (t / 60 % (math.pi * 2)), (t / 40 % (math.pi * 2)))
)
for i = 1, #rings do
rings[i](
CFrame.Angles(0, 1 / 4 + (t / 60 % (math.pi * 2)), 0)
)
end
end
end)
local connection;
local self = {}
function self:SetEnabled(enabled)
if enabled and connection then
return
end
if connection then
connection:disconnect()
connection = nil
end
if not enabled then
return
end
local timestamp0 = tick()
connection = game:GetService("RunService").RenderStepped:connect(function()
local t = (tick() - timestamp0)
if renderStation then
renderStation(t)
end
if renderOrbit then
renderOrbit(t)
end
end)
end
return self
@@ -0,0 +1,70 @@
local runService = game:GetService("RunService")
local Volcano;
local lightning;
spawn(function()
while not runService:IsRunning() do wait(0.1) end
Volcano = workspace:WaitForChild("Zones"):WaitForChild("Volcano")
lightning = Volcano.Lightning:GetChildren()
for i = 1, #lightning do
lightning[i].Parent = nil
end
end)
local function playLighting(model)
if not (model and Volcano) then
return
end
model.Parent = Volcano.Lightning
wait(0.125)
model.Parent = nil
end
local connection;
local handle;
local self = {}
function self:SetEnabled(enabled)
if enabled and connection then
return
end
if connection then
connection:disconnect()
connection = nil
end
handle = nil
if not enabled then
return
end
local h = {} -- our handle
handle = h -- Stops a previous loop
coroutine.wrap(function()
while handle == h do
wait(math.random() * 16 + 1)
if lightning then
playLighting(lightning[math.random(1, #lightning)])
end
end
end)()
--[[
local timestamp0 = tick()
connection = game:GetService("RunService").RenderStepped:connect(function()
local t = (tick() - timestamp0)
--renderStation(t)
--renderOrbit(t)
end)
--]]
end
return self
@@ -0,0 +1,285 @@
--[[
// CarouselController.lua
// Controls how the data is updated for a carousel view
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService('ContextActionService')
local GuiService = game:GetService('GuiService')
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function createCarouselController(view)
local this = {}
local PAGE_SIZE = 25
local MAX_PAGES = 2
local LOAD_BUFFER = 10
local LOAD_AMOUNT = 100
local sortCollection = nil
local sortDataObject = nil -- TODO: Remove when caching is finished
local sortData = nil
local currentFocusData = nil
local sortDataIndex = 1
local guiServiceChangedCn = nil
local pages = {}
local firstIndex = 0
local lastIndex = 0
local isLoading = false
-- Events
this.NewItemSelected = Utility.Signal()
local function getNewItem(data)
local item = Utility.Create'ImageButton'
{
Name = "CarouselViewImage";
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
SoundManager:CreateSound('MoveSelection');
}
spawn(function()
local loader = ThumbnailLoader:Create(item, data.IconId, ThumbnailLoader.Sizes.Medium, ThumbnailLoader.AssetType.Icon)
loader:LoadAsync(true, false, nil)
end)
return item
end
-- TODO: Remove this when caching is finished. Doing it this way is going to leave the old
-- data issues in place with the carousel
local function setInternalData(page)
if not page then
return {}
end
local newData = {}
local placeIds = page:GetPagePlaceIds()
local names = page:GetPagePlaceNames()
local voteData = page:GetPageVoteData()
local iconIds = page:GetPageIconIds()
local creatorNames = page:GetCreatorNames()
local creatorUserIds = page:GetPageCreatorUserIds()
for i = 1, #page.Data do
local gameEntry = {
Title = names[i];
PlaceId = placeIds[i];
IconId = iconIds[i];
VoteData = voteData[i];
CreatorName = creatorNames[i];
CreatorUserId = creatorUserIds[i];
-- Description and IsFavorites needs to be queried for each game when needed
Description = nil;
IsFavorited = nil;
GameData = nil;
}
table.insert(newData, gameEntry)
end
return newData
end
local function createPage(startIndex)
local page = {}
local items = {}
for i = 1, PAGE_SIZE do
local dataIndex = startIndex + (i - 1)
if sortData[dataIndex] then
local item = getNewItem(sortData[dataIndex])
item.MouseButton1Click:connect(function()
local currentData = sortData[dataIndex]
if currentData then
EventHub:dispatchEvent(EventHub.Notifications["OpenGameDetail"], currentData.PlaceId, currentData.Title, currentData.IconId, currentData.GameData)
end
end)
table.insert(items, item)
end
end
function page:GetCount()
return #items
end
function page:GetItems()
return items
end
function page:Destroy()
for i,item in pairs(items) do
items[i] = nil
item:Destroy()
end
end
return page
end
local previousFocusItem = nil
local function onViewFocusChanged(newFocusItem)
local offset = 0
if previousFocusItem then
offset = view:GetItemIndex(newFocusItem) - view:GetItemIndex(previousFocusItem)
end
local visibleItemCount = view:GetVisibleCount()
local itemCount = view:GetCount()
if offset > 0 then
-- scrolled right
local firstVisibleItemIndex = view:GetFirstVisibleItemIndex()
if not isLoading and firstVisibleItemIndex + visibleItemCount + LOAD_BUFFER >= itemCount then
isLoading = true
local page = createPage(lastIndex + 1)
if page:GetCount() > 0 then
local items = page:GetItems()
view:InsertCollectionBack(items)
table.insert(pages, page)
lastIndex = lastIndex + page:GetCount()
-- remove front page
if view:GetCount() > PAGE_SIZE * MAX_PAGES then
local firstPage = table.remove(pages, 1)
firstIndex = firstIndex + firstPage:GetCount()
view:RemoveAmountFromFront(firstPage:GetCount())
firstPage:Destroy()
end
end
isLoading = false
end
elseif offset < 0 then
-- scrolled left
local lastVisibleItemIndex = view:GetLastVisibleItemIndex()
if not isLoading and lastVisibleItemIndex - visibleItemCount - LOAD_BUFFER < 0 then
isLoading = true
local page = createPage(firstIndex - PAGE_SIZE)
if page:GetCount() > 0 then
local items = page:GetItems()
view:InsertCollectionFront(items)
table.insert(pages, 1, page)
firstIndex = firstIndex - page:GetCount()
if view:GetCount() > PAGE_SIZE * MAX_PAGES then
local lastPage = table.remove(pages)
lastIndex = lastIndex - lastPage:GetCount()
view:RemoveAmountFromBack(lastPage:GetCount())
lastPage:Destroy()
end
end
isLoading = false
end
end
sortDataIndex = sortDataIndex + offset
previousFocusItem = newFocusItem
view:ChangeFocus(newFocusItem)
currentFocusData = sortData[sortDataIndex]
this.NewItemSelected:fire(currentFocusData)
end
--[[ Public API ]]--
function this:GetCurrentFocusGameData()
return currentFocusData
end
function this:InitializeAsync(gameCollection)
view:RemoveAllItems()
firstIndex = 1
lastIndex = 1
currentFocusData = nil
pages = {}
sortData = {}
sortCollection = gameCollection
local page = gameCollection:GetSortAsync(0, LOAD_AMOUNT)
sortData = setInternalData(page)
local firstPage = createPage(firstIndex)
if firstPage:GetCount() > 0 then
local items = firstPage:GetItems()
view:InsertCollectionBack(items)
table.insert(pages, firstPage)
lastIndex = firstPage:GetCount()
local frontViewItem = view:GetFront()
if frontViewItem then
previousFocusItem = frontViewItem
sortDataIndex = 1
currentFocusData = sortData[sortDataIndex]
view:SetFocus(frontViewItem)
end
end
end
function this:Connect()
guiServiceChangedCn = Utility.DisconnectEvent(guiServiceChangedCn)
guiServiceChangedCn = GuiService.Changed:connect(function(property)
if property ~= 'SelectedCoreObject' then
return
end
local newSelection = GuiService.SelectedCoreObject
if newSelection and view:ContainsItem(newSelection) then
onViewFocusChanged(newSelection)
end
end)
local seenRightBumper = false
local function onBumperRight(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenRightBumper = true
elseif seenRightBumper and inputState == Enum.UserInputState.End then
local currentSelection = GuiService.SelectedCoreObject
if currentSelection and view:ContainsItem(currentSelection) then
local currentFocusIndex = view:GetItemIndex(previousFocusItem)
local shiftAmount = view:GetFullVisibleItemCount()
local nextItem = view:GetItemAt(currentFocusIndex + shiftAmount) or view:GetBack()
if nextItem then
GuiService.SelectedCoreObject = nextItem
end
end
seenRightBumper = false
end
end
local seenLeftBumper = false
local function onBumperLeft(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
seenLeftBumper = true
elseif seenLeftBumper and inputState == Enum.UserInputState.End then
local currentSelection = GuiService.SelectedCoreObject
if currentSelection and view:ContainsItem(currentSelection) then
local currentFocusIndex = view:GetItemIndex(previousFocusItem)
local shiftAmount = view:GetFullVisibleItemCount()
local nextItem = view:GetItemAt(currentFocusIndex - shiftAmount) or view:GetFront()
if nextItem then
GuiService.SelectedCoreObject = nextItem
end
end
seenRightBumper = false
end
end
-- Bumper Binds
ContextActionService:BindCoreAction("BumperRight", onBumperRight, false, Enum.KeyCode.ButtonR1)
ContextActionService:BindCoreAction("BumperLeft", onBumperLeft, false, Enum.KeyCode.ButtonL1)
end
function this:Disconnect()
guiServiceChangedCn = Utility.DisconnectEvent(guiServiceChangedCn)
ContextActionService:UnbindCoreAction("BumperRight")
ContextActionService:UnbindCoreAction("BumperLeft")
end
return this
end
return createCarouselController
@@ -0,0 +1,292 @@
--[[
// CarouselView.lua
// View for a carousel. Used for GameGenre screen
// TODO: Support Vertical?
//
// Current this supports a focus that is aligned to the left (0, 0), in the future we
// could do other alignments if we need them
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local function createCarouselView()
local this = {}
local items = {}
local padding = 0
local itemSizePercentOfContainer = 1
local focusItem = nil
local BASE_TWEEN_TIME = 0.2
local container = Utility.Create'ScrollingFrame'
{
Name = "CarouselContainer";
BackgroundTransparency = 1;
ClipsDescendants = false;
ScrollingEnabled = false;
Selectable = false;
ScrollBarThickness = 0;
}
local function isVisible(item)
return item.AbsolutePosition.x + item.AbsoluteSize.x >= 0 and
item.AbsolutePosition.x < GuiRoot.AbsoluteSize.x
end
local function getFocusSize()
local size = container.Size.Y.Offset
return UDim2.new(0, size, 0, size)
end
local function getNonFocusSize()
local size = container.Size.Y.Offset * itemSizePercentOfContainer
return UDim2.new(0, size, 0, size)
end
local function getItemSize(item)
if item == focusItem then
return getFocusSize()
else
return getNonFocusSize()
end
end
local function getItemLayoutPosition(index)
local focusIndex = this:GetItemIndex(focusItem)
local offsetFromFocus = index - focusIndex
local x, y = 0, 0
if index > focusIndex then
-- items to the right of focus need additional buffer due to focus size being larger
x = getFocusSize().X.Offset + offsetFromFocus * padding + (offsetFromFocus - 1) * getNonFocusSize().X.Offset
else
x = offsetFromFocus * padding + offsetFromFocus * getNonFocusSize().X.Offset
end
y = (index == focusIndex and 0) or (container.Size.Y.Offset - getNonFocusSize().Y.Offset) / 2
return UDim2.new(0, x, 0, y)
end
local function recalcLayout()
for i = 1, #items do
local item = items[i]
local size = getItemSize(item)
local position = getItemLayoutPosition(i)
if item:IsDescendantOf(game.Workspace) then
item:TweenSizeAndPosition(size, position, Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0, true)
else
item.Size = size
item.Position = position
end
end
end
--[[ Public API ]]--
function this:ChangeFocus(newFocus)
-- We don't use SetFocus() as that function will do a recalc. We want to get the next position
-- from current position, not recalcd postion for each item
if self:ContainsItem(newFocus) then
focusItem = newFocus
for i = 1, #items do
local item = items[i]
item:TweenSizeAndPosition(getItemSize(item), getItemLayoutPosition(i), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, BASE_TWEEN_TIME, true)
end
end
end
function this:SetSize(newSize)
if newSize ~= container.Size then
container.Size = newSize
container.CanvasSize = UDim2.new(0, container.Size.X.Offset * 2, 1, 0)
recalcLayout()
end
end
function this:SetPosition(newPosition)
container.Position = newPosition
end
function this:SetPadding(newPadding)
if newPadding ~= padding then
padding = newPadding
recalcLayout()
end
end
function this:SetItemSizePercentOfContainer(value)
if value ~= itemSizePercentOfContainer then
itemSizePercentOfContainer = value
recalcLayout()
end
end
function this:SetParent(newParent)
container.Parent = newParent
end
function this:SetFocus(newFocusItem)
if self:ContainsItem(newFocusItem) and newFocusItem ~= focusItem then
focusItem = newFocusItem
recalcLayout()
end
end
function this:GetFocusItem()
return focusItem
end
function this:GetItemAt(index)
return items[index]
end
function this:GetFront()
return items[1]
end
function this:GetBack()
return items[#items]
end
function this:GetItemIndex(item)
for i = 1, #items do
if items[i] == item then
return i
end
end
return 0
end
function this:GetCount()
return #items
end
function this:GetVisibleCount()
local visibleItemCount = 0
for i = 1, #items do
if isVisible(items[i]) then
visibleItemCount = visibleItemCount + 1
end
end
return visibleItemCount
end
function this:GetFirstVisibleItemIndex()
for i = 1, #items do
if isVisible(items[i]) then
return i
end
end
end
function this:GetLastVisibleItemIndex()
for i = #items, 1, -1 do
if isVisible(items[i]) then
return i
end
end
end
function this:GetFullVisibleItemCount()
local containerSizeX = container.AbsoluteSize.x
-- remove focus from the size, and figure out how many other items can fit
local fittingSize = containerSizeX - getFocusSize().X.Offset
if fittingSize <= 0 then
return 0
end
local itemSize = getNonFocusSize().X.Offset + padding
local count = math.floor(fittingSize/itemSize) + 1
return count
end
function this:InsertCollectionFront(collection)
for i = #collection, 1, -1 do
local item = collection[i]
table.insert(items, 1, item)
item.Parent = container
end
recalcLayout()
end
function this:InsertCollectionBack(collection)
for i = 1, #collection do
local item = collection[i]
table.insert(items, item)
item.Parent = container
end
recalcLayout()
end
function this:RemoveAmountFromFront(amount)
for i = 1, amount do
local item = table.remove(items, 1)
item.Parent = nil
end
recalcLayout()
end
function this:RemoveAmountFromBack(amount)
for i = 1, amount do
local item = table.remove(items)
item.Parent = nil
end
recalcLayout()
end
function this:InsertFront(newItem)
table.insert(items, 1, newItem)
newItem.Parent = container
recalcLayout()
end
function this:InsertBack(newItem)
table.insert(items, newItem)
newItem.Parent = container
recalcLayout()
end
function this:RemoveFront()
local item = table.remove(items, 1)
item.Parent = nil
recalcLayout()
end
function this:RemoveBack()
local item = table.remove(items, #items)
item.Parent = nil
recalcLayout()
end
function this:RemoveItem(item)
for i = 1, #items do
if items[i] == item then
local removedItem = table.remove(items, i)
removedItem.Parent = nil
break
end
end
recalcLayout()
end
function this:RemoveAllItems()
for i = #items, 1, -1 do
local item = table.remove(items, #items)
item.Parent = nil
end
end
function this:ContainsItem(item)
if not item then
return false
end
return item.Parent == container
end
return this
end
return createCarouselView
@@ -0,0 +1,449 @@
--[[
// ConfirmPrompt.lua
// Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local MarketplaceService = game:GetService('MarketplaceService')
local UserDataModule = require(Modules:FindFirstChild('UserData'))
local Http = require(Modules:FindFirstChild('Http'))
local ScrollingTextBox = require(Modules:FindFirstChild('ScrollingTextBox'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local CurrencyWidgetModule = require(Modules:FindFirstChild('CurrencyWidget'))
local MOCKUP_WIDTH = 1920
local MOCKUP_HEIGHT = 1080
local CONTENT_WIDTH = 1920
local CONTENT_HEIGHT = 690
local PACKAGE_CONTAINER_WIDTH = 780
local PACKAGE_CONTAINER_HEIGHT = 690
local PACKAGE_BACKGROUND_WIDTH = 580
local PACKAGE_BACKGROUND_HEIGHT = 640
local CONTENT_POSITION = Vector2.new(0, 225)
local DETAILS_CONTAINER_WIDTH = CONTENT_WIDTH - PACKAGE_CONTAINER_WIDTH
local DETAILS_CONTAINER_HEIGHT = 690
local DESCRIPTION_WIDTH = 800
local DESCRIPTION_HEIGHT = 265
local BUY_BUTTON_WIDTH = 320
local BUY_BUTTON_HEIGHT = 64
local TEXT_START_OFFSET = Vector2.new(0, 135)
local TEXT_SPACING = Vector2.new(0, 20)
local BUY_BUTTON_OFFSET = Vector2.new(0, -50)
local ROBUX_BALANCE_OFFSET = Vector2.new(100, -130)
local DELAY_BEFORE_PURCHASE = 1
local function CreateConfirmPrompt(confirmDetails, properties)
local this = {}
properties = properties or {}
local MyParent = nil
local InFocus = false
local Result = nil
local ResultEvent = Utility.Signal()
local OnResultCallbacks = {}
local productName = confirmDetails.ProductName
local ConfirmPrompt = Utility.Create'Frame'
{
Name = "ConfirmPrompt";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local ContentContainer = Utility.Create'Frame'
{
Name = "ContentContainer";
Size = UDim2.new(CONTENT_WIDTH/MOCKUP_WIDTH, 0, CONTENT_HEIGHT/MOCKUP_HEIGHT, 0);
Position = UDim2.new(CONTENT_POSITION.x/MOCKUP_WIDTH, 0, CONTENT_POSITION.y/MOCKUP_HEIGHT, 0);
BackgroundTransparency = 0;
BackgroundColor3 = GlobalSettings.OverlayColor;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ConfirmPrompt;
}
local PackageContainer = Utility.Create'Frame'
{
Name = "PackageContainer";
Size = UDim2.new(PACKAGE_CONTAINER_WIDTH/CONTENT_WIDTH, 0, PACKAGE_CONTAINER_HEIGHT/CONTENT_HEIGHT, 0);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ContentContainer;
}
local PackageBackground = Utility.Create'Frame'
{
Name = "PackageBackground";
Size = UDim2.new(PACKAGE_BACKGROUND_WIDTH/PACKAGE_CONTAINER_WIDTH, 0, PACKAGE_BACKGROUND_HEIGHT/CONTENT_HEIGHT, 0);
BackgroundTransparency = 0;
BackgroundColor3 = GlobalSettings.ForegroundGreyColor;
BorderSizePixel = 0;
ZIndex = 4;
Parent = PackageContainer;
AssetManager.CreateShadow(3);
}
Utility.CalculateAnchor(PackageBackground, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local PackageClipper = Utility.Create'Frame'
{
Name = "PackageClipper";
Size = UDim2.new(1,0,1,0);
BackgroundTransparency = 1;
ClipsDescendants = true;
Parent = PackageBackground;
}
local PackageImage = Utility.Create'ImageLabel'
{
Name = 'PackageImage';
Size = UDim2.new(1,0,1,0);
Image = ''; --Http.GetThumbnailUrlForAsset(assetId);
BackgroundTransparency = 1;
ZIndex = 5;
Parent = PackageClipper;
};
if confirmDetails and confirmDetails.ProductImage then
PackageImage.Image = confirmDetails.ProductImage
else
PackageBackground.Visible = false
end
local DetailsContainer = Utility.Create'Frame'
{
Name = "DetailsContainer";
Size = UDim2.new(DETAILS_CONTAINER_WIDTH/CONTENT_WIDTH, 0, DETAILS_CONTAINER_HEIGHT/CONTENT_HEIGHT, 0);
Position = UDim2.new(PACKAGE_CONTAINER_WIDTH/CONTENT_WIDTH,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ContentContainer;
}
local ConfirmTitle = Utility.Create'TextLabel'
{
Name = 'ConfirmTitle';
Text = Strings:LocalizedString('ConfirmPurchaseTitle');
Position = UDim2.new(0, 0, 0, 66);
Size = UDim2.new(1,0,0,25);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.HeadingFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = DetailsContainer;
};
local formattedPackageCost = "";
if confirmDetails.Cost then
if type(confirmDetails.Cost) == 'string' then
formattedPackageCost = confirmDetails.Cost
else
formattedPackageCost = (confirmDetails.CurrencySymbol or '') .. Utility.FormatNumberString(confirmDetails.Cost)
end
end
local RobuxIcon = Utility.Create'ImageLabel'
{
Name = 'RobuxIcon';
Position = UDim2.new(0,0,0,125);
Size = UDim2.new(0,(properties.ShowRobuxIcon == true) and 50 or 0,0,50);
BackgroundTransparency = 1;
ZIndex = 4;
Parent = DetailsContainer;
};
if confirmDetails.Currency == "ROBUX" then
AssetManager.LocalImage(RobuxIcon, 'rbxasset://textures/ui/Shell/Icons/ROBUXIcon', {['720'] = UDim2.new(0,28,0,28); ['1080'] = UDim2.new(0,42,0,42);})
end
local PackageCost = Utility.Create'TextLabel'
{
Name = 'PackageCost';
Text = formattedPackageCost;
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1.3,0,0,0);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.GreenTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = RobuxIcon;
};
if confirmDetails and confirmDetails.Cost and confirmDetails.Cost == 0 then
PackageCost.Text = Strings:LocalizedString('FreeWord'):upper()
end
local areYouSurePhrase;
if confirmDetails.Cost and confirmDetails.Cost == 0 then
areYouSurePhrase = string.format(Strings:LocalizedString('AreYouSureTakePhrase'), tostring(productName))
elseif properties.ConfirmWithPrice then
areYouSurePhrase = string.format(Strings:LocalizedString('AreYouSureWithPricePhrase'), tostring(productName), tostring(formattedPackageCost))
else
areYouSurePhrase = string.format(Strings:LocalizedString('AreYouSurePhrase'), tostring(productName))
end
local ConfirmItemDetail = Utility.Create'TextLabel'
{
Name = 'ConfirmItemDetail';
Text = areYouSurePhrase;
Position = UDim2.new(0, 0, 0, 205);
Size = UDim2.new(1,0,0,25);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = DetailsContainer;
};
local RemaningBalance = Utility.Create'TextLabel'
{
Name = 'RemaningBalance';
Text = '';
Position = UDim2.new(0, 0, 0, 285);
Size = UDim2.new(1,0,0,25);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
BackgroundTransparency = 1;
ZIndex = 4;
Visible = properties.ShowRemainingBalance == true;
Parent = DetailsContainer;
};
local ConfirmButton = Utility.Create'ImageButton'
{
Name = "ConfirmButton";
Size = UDim2.new(BUY_BUTTON_WIDTH/DETAILS_CONTAINER_WIDTH, 0, BUY_BUTTON_HEIGHT/DETAILS_CONTAINER_HEIGHT, 0);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
BackgroundTransparency = 0;
ZIndex = 4;
Parent = DetailsContainer;
}
Utility.CalculateAnchor(ConfirmButton, UDim2.new(0, 0, 1 + BUY_BUTTON_OFFSET.Y/DETAILS_CONTAINER_HEIGHT, 0), Utility.Enum.Anchor.BottomLeft)
local ConfirmText = Utility.Create'TextLabel'
{
Name = 'ConfirmText';
Text = Strings:LocalizedString('ConfirmWord'):upper();
Size = UDim2.new(1,0,1,0);
TextColor3 = GlobalSettings.TextSelectedColor;
Font = GlobalSettings.HeadingFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = ConfirmButton;
};
local function SetResult(value)
Result = value
while #OnResultCallbacks > 0 do
local callback = table.remove(OnResultCallbacks, #OnResultCallbacks)
callback(Result)
end
end
local function Decline()
if this == ScreenManager:GetTopScreen() then
SetResult(false)
ScreenManager:CloseCurrent()
end
end
local function Confirm()
if this == ScreenManager:GetTopScreen() then
SetResult(true)
ScreenManager:CloseCurrent()
end
end
function this:ResultAsync()
if Result then
return Result
end
ResultEvent:wait()
return Result
end
function this:AddResultCallback(callback)
if Result ~= nil then
callback(Result)
else
table.insert(OnResultCallbacks, callback)
end
end
function this:FadeInBackground()
Utility.PropertyTweener(ConfirmPrompt, "BackgroundTransparency", 1, GlobalSettings.ModalBackgroundTransparency, 0.25, Utility.EaseInOutQuad, true)
end
function this:GetDefaultSelectableObject()
return ConfirmButton
end
local currencyWidget = nil
local RobuxChangedConn = nil
function this:Show()
ConfirmPrompt.Visible = true
ConfirmPrompt.Parent = MyParent
if self.BackgroundTween then
self.BackgroundTween:Cancel()
end
self.BackgroundTween = Utility.PropertyTweener(ConfirmPrompt, "BackgroundTransparency", 1, GlobalSettings.ModalBackgroundTransparency, 0, Utility.EaseInOutQuad, nil)
SoundManager:Play('OverlayOpen')
local function onPackageBackgroundResize()
local rawImageSize = Vector2.new(420, 420)
if confirmDetails.ProductImageSize then
rawImageSize = confirmDetails.ProductImageSize
end
PackageImage.Size = Utility.CalculateFill(PackageBackground, rawImageSize)
Utility.CalculateAnchor(PackageImage, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
end
self.PackageBackgroundChangedConn = Utility.DisconnectEvent(self.PackageBackgroundChangedConn)
self.PackageBackgroundChangedConn = PackageBackground.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
onPackageBackgroundResize()
end
end)
onPackageBackgroundResize()
local function onBalanceLoaded(newBalance)
local balance = newBalance
if properties.ShowRemainingBalance then
if balance and confirmDetails and confirmDetails.Cost then
if confirmDetails.Cost and confirmDetails.Cost > 0 then
local newBalance = balance and confirmDetails.Cost and balance - confirmDetails.Cost
RemaningBalance.Text = string.format(Strings:LocalizedString('RemainingBalancePhrase'), Utility.FormatNumberString(tostring(newBalance)));
else
RemaningBalance.Text = ''
end
end
end
end
if confirmDetails.Balance then
onBalanceLoaded(confirmDetails.Balance)
else
spawn(function()
local balance = UserDataModule.GetPlatformUserBalanceAsync()
onBalanceLoaded(balance)
end)
end
if not currencyWidget then
currencyWidget = CurrencyWidgetModule({Parent = ConfirmPrompt; Position = UDim2.new(0.052, 0, 0.88, 0); ZIndex = 4;})
end
Utility.DisconnectEvent(RobuxChangedConn)
RobuxChangedConn = currencyWidget.RobuxChanged:connect(onBalanceLoaded)
end
function this:Hide()
ConfirmPrompt.Visible = false
ConfirmPrompt.Parent = nil
if self.BackgroundTween then
self.BackgroundTween:Cancel()
end
self.BackgroundTween = nil
self.PackageBackgroundChangedConn = Utility.DisconnectEvent(self.PackageBackgroundChangedConn)
RobuxChangedConn = Utility.DisconnectEvent(RobuxChangedConn)
end
function this:ScreenRemoved()
if currencyWidget then
currencyWidget:Destroy()
currencyWidget = nil
end
if Result == nil then
SetResult(false)
end
ResultEvent:fire(Result)
end
function this:Focus()
InFocus = true
ContextActionService:BindCoreAction("ReturnFromCurrentConfirmScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
Decline()
end
end,
false,
Enum.KeyCode.ButtonB)
local isConfirmingPurchase = false
self.ConfirmButtonConn = Utility.DisconnectEvent(self.ConfirmButtonConn)
self.ConfirmButtonConn = ConfirmButton.MouseButton1Click:connect(function()
if isConfirmingPurchase then return end
isConfirmingPurchase = true
SoundManager:Play('ButtonPress')
Confirm()
isConfirmingPurchase = false
end)
GuiService:AddSelectionParent("ConfirmOptionsSelectionGroup", ContentContainer)
-- spawn(function()
-- wait(DELAY_BEFORE_PURCHASE) -- Stop quick a-button smashing from purchasing items
if InFocus and GuiService.SelectedCoreObject == nil then
GuiService.SelectedCoreObject = self:GetDefaultSelectableObject()
end
-- end)
end
function this:RemoveFocus()
ContextActionService:UnbindCoreAction("ReturnFromCurrentConfirmScreen")
GuiService:RemoveSelectionGroup("ConfirmOptionsSelectionGroup")
self.ConfirmButtonConn = Utility.DisconnectEvent(self.ConfirmButtonConn)
GuiService.SelectedCoreObject = nil
InFocus = false
end
function this:SetParent(parent)
MyParent = parent
ConfirmPrompt.Parent = MyParent
end
return this
end
return CreateConfirmPrompt
@@ -0,0 +1,131 @@
--[[
// ControllerStateManager.lua
// Handles controller state changes
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService('UserInputService')
local EventHub = require(Modules:FindFirstChild('EventHub'))
local NoActionOverlay = require(Modules:FindFirstChild('NoActionOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local UserData = require(Modules:FindFirstChild('UserData'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ControllerStateManager = {}
local LostUserGamepadCn = nil
local LostActiveUserCn = nil
local GainedUserGamepadCn = nil
local GainedActiveUserCn = nil
local DisconnectCn = nil
local currentOverlay = nil
local DATAMODEL_TYPE = {
APP_SHELL = 0;
GAME = 1;
}
local function closeOverlay(dataModelType)
if dataModelType == DATAMODEL_TYPE.GAME then
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.None
currentOverlay:Hide()
currentOverlay = nil
else
ScreenManager:CloseCurrent()
end
end
local function showErrorOverlay(titleName, bodyName, userDisplayName, dataModelType)
-- create error
local err = { Title = Strings:LocalizedString(titleName),
Msg = string.format(Strings:LocalizedString(bodyName), userDisplayName) }
local noActionOverlay = NoActionOverlay(err)
if dataModelType == DATAMODEL_TYPE.GAME then
UserInputService.OverrideMouseIconBehavior = Enum.OverrideMouseIconBehavior.ForceHide
currentOverlay = noActionOverlay
noActionOverlay:Show()
else
ScreenManager:OpenScreen(noActionOverlay, false)
end
end
local function onLostActiveUser(userDisplayName, dataModelType)
showErrorOverlay("ActiveUserLostConnectionTitle", "ActiveUserLostConnectionPhrase", userDisplayName, dataModelType)
end
local function onGainedActiveUser(dataModelType)
closeOverlay(dataModelType)
end
local function onLostUserGamepad(userDisplayName, dataModelType)
showErrorOverlay("ControllerLostConnectionTitle", "ControllerLostConnectionPhrase", userDisplayName, dataModelType)
end
local function onGainedUserGamepad(dataModelType)
closeOverlay(dataModelType)
end
local function disconnectEvents()
LostActiveUserCn = Utility.DisconnectEvent(LostActiveUserCn)
GainedActiveUserCn = Utility.DisconnectEvent(GainedActiveUserCn)
LostUserGamepadCn = Utility.DisconnectEvent(LostUserGamepadCn)
GainedUserGamepadCn = Utility.DisconnectEvent(GainedUserGamepadCn)
end
function ControllerStateManager:Initialize()
if not PlatformService then return end
local dataModelType = PlatformService.DatamodelType
disconnectEvents()
LostUserGamepadCn = PlatformService.LostUserGamepad:connect(function(userDisplayName)
onLostUserGamepad(userDisplayName, dataModelType)
end)
GainedUserGamepadCn = PlatformService.GainedUserGamepad:connect(function(userDisplayName)
onGainedUserGamepad(dataModelType)
end)
LostActiveUserCn = PlatformService.LostActiveUser:connect(function(userDisplayName)
onLostActiveUser(userDisplayName, dataModelType)
end)
GainedActiveUserCn = PlatformService.GainedActiveUser:connect(function(userDisplayName)
onGainedActiveUser(dataModelType)
end)
-- disconnect based on DataModel type
if dataModelType == DATAMODEL_TYPE.GAME then
DisconnectCn = PlatformService.ViewChanged:connect(function(viewType)
if viewType == 0 then
disconnectEvents()
end
end)
else
DisconnectCn = PlatformService.UserAccountChanged:connect(function(reauthenticationReason)
disconnectEvents()
end)
end
end
function ControllerStateManager:CheckUserConnected()
if not PlatformService then return end
local isGamepadConnected = UserInputService:GetGamepadConnected(Enum.UserInputType.Gamepad1)
local dataModelType = PlatformService.DatamodelType
if not isGamepadConnected then
local userInfo = PlatformService:GetPlatformUserInfo()
local userDisplayName = userInfo["Gamertag"] or ""
onLostUserGamepad(userDisplayName, dataModelType)
end
end
return ControllerStateManager
@@ -0,0 +1,257 @@
--[[
// CurrencyWidget.lua by Kip Turner
--]]
local TextService = game:GetService('TextService')
local PlatformService;
pcall(function() PlatformService = game:GetService('PlatformService') end)
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local UserDataModule = require(Modules:FindFirstChild('UserData'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local EventHubConnectCount = 0
local InternalPlatformRobuxAmountChangedSignal = Utility.Signal()
local InternalTotalRobuxAmountChangedSignal = Utility.Signal()
local function CreateCurrencyWidget(properties)
properties = properties or {}
local this = {}
this.RobuxChanged = Utility.Signal()
local internalPlatformRobuxChangedConn = nil
local internalTotalRobuxChangedConn = nil
local CachedTotalRobuxValue = nil
local CachedRobuxValue = nil
local destroyed = false
EventHubConnectCount = EventHubConnectCount + 1
local myEventId = "CurrencyWidget" .. tostring(EventHubConnectCount)
local RobuxBalanceTitle = Utility.Create'TextLabel'
{
Name = 'RobuxBalanceTitle';
Size = UDim2.new(0,0,0,0);
Position = properties.Position or UDim2.new(0, 0, 0, 0);
TextXAlignment = 'Left';
TextYAlignment = 'Top';
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString('RobuxBalanceTitle') .. ':';
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = properties.ZIndex or 2;
Parent = properties.Parent or nil;
};
local robuxTitleSize = TextService:GetTextSize(RobuxBalanceTitle.Text, Utility.ConvertFontSizeEnumToInt(RobuxBalanceTitle.FontSize), RobuxBalanceTitle.Font, Vector2.new())
RobuxBalanceTitle.Size = UDim2.new(0, robuxTitleSize.X, 0, robuxTitleSize.Y)
local RobuxBalanceIcon = Utility.Create'ImageLabel'
{
Name = 'RobuxIcon';
Position = UDim2.new(1,10,0,0);
Size = UDim2.new(0,46,0,46);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/ROBUXIconOutlined@1080.png';
ZIndex = properties.ZIndex or 2;
Parent = RobuxBalanceTitle;
};
Utility.CalculateAnchor(RobuxBalanceIcon, UDim2.new(1,17,0.5,0), Utility.Enum.Anchor.CenterLeft)
local RobuxBalanceValue = Utility.Create'TextLabel'
{
Name = 'RobuxBalanceValue';
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1,5,0,-2);
Text = '';
TextXAlignment = 'Left';
TextYAlignment = 'Center';
TextColor3 = GlobalSettings.GreenTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = properties.ZIndex or 2;
TextTransparency = 1;
Parent = RobuxBalanceIcon;
};
local function UpdateBalanceText()
local balanceValueString = CachedRobuxValue and Utility.FormatNumberString(tostring(CachedRobuxValue)) or '-'
local balanceStringWidth = TextService:GetTextSize(balanceValueString, Utility.ConvertFontSizeEnumToInt(RobuxBalanceValue.FontSize), RobuxBalanceValue.Font, Vector2.new())
RobuxBalanceValue.Size = UDim2.new(0, balanceStringWidth.X, 1, 0)
RobuxBalanceValue.Text = balanceValueString
if RobuxBalanceValue.TextTransparency == 1 and CachedRobuxValue ~= nil then
Utility.PropertyTweener(RobuxBalanceValue, 'TextTransparency', 1, 0, 0.5, Utility.EaseOutQuad, true)
end
end
local robuxAmountChangedLoader = nil
local RobuxChangedConn = nil
if PlatformService then
local robuxChangedEventCount = 0
local function fetchNewRobuxAsync(thisEventCount)
if not (CachedRobuxValue and CachedTotalRobuxValue) then
this:RefreshRobuxAmountAsync()
end
local prepurchaseRobux = CachedRobuxValue
local prepurchaseTotalRobux = CachedTotalRobuxValue
Utility.ExponentialRepeat(
function() return thisEventCount == robuxChangedEventCount and not destroyed end,
function()
local balance = UserDataModule.GetPlatformUserBalanceAsync()
local totalBalance = UserDataModule.GetTotalUserBalanceAsync()
if balance and totalBalance and not destroyed then
if balance ~= prepurchaseRobux and totalBalance ~= prepurchaseTotalRobux then
CachedRobuxValue = balance
CachedTotalRobuxValue = totalBalance
UpdateBalanceText()
if this.RobuxChanged ~= nil then
this.RobuxChanged:fire(balance)
end
return true
end
end
end
)
end
local function OnRobuxAmountChanged()
robuxChangedEventCount = robuxChangedEventCount + 1
local thisEventCount = robuxChangedEventCount
if robuxAmountChangedLoader then
robuxAmountChangedLoader:Cleanup()
robuxAmountChangedLoader = nil
end
local loader = LoadingWidget({Parent = RobuxBalanceValue, Size = UDim2.new(0,50,0,50), Position = UDim2.new(1,75,0,25)}, {function() fetchNewRobuxAsync(thisEventCount) end})
robuxAmountChangedLoader = loader
robuxAmountChangedLoader:AwaitFinished()
if robuxAmountChangedLoader and loader == robuxAmountChangedLoader then
robuxAmountChangedLoader:Cleanup()
robuxAmountChangedLoader = nil
end
end
Utility.DisconnectEvent(RobuxChangedConn)
RobuxChangedConn = PlatformService.RobuxAmountChanged:connect(function(platformPurchaseResult)
if platformPurchaseResult == 3 then
OnRobuxAmountChanged()
else
if robuxAmountChangedLoader then
robuxAmountChangedLoader:Cleanup()
robuxAmountChangedLoader = nil
end
end
end)
EventHub:addEventListener(EventHub.Notifications["RobuxCatalogPurchaseInitiated"], myEventId, function()
-- print("CurrencyWidget: on robux amoutn changed")
OnRobuxAmountChanged()
end)
end
function this:GetAbsoluteSize()
return (RobuxBalanceValue.AbsolutePosition + RobuxBalanceValue.AbsoluteSize) - (RobuxBalanceTitle.AbsolutePosition)
end
function this:GetRobuxAmount()
return CachedRobuxValue
end
function this:RefreshRobuxAmountAsync()
local beforeTotalRobuxValue = CachedTotalRobuxValue
local beforeRobuxValue = CachedRobuxValue
UserDataModule.GetLocalUserIdAsync()
CachedTotalRobuxValue = UserDataModule.GetTotalUserBalanceAsync()
CachedRobuxValue = UserDataModule.GetPlatformUserBalanceAsync()
UpdateBalanceText()
if beforeTotalRobuxValue ~= CachedTotalRobuxValue then
InternalTotalRobuxAmountChangedSignal:fire(CachedTotalRobuxValue)
end
if beforeRobuxValue ~= CachedRobuxValue then
InternalPlatformRobuxAmountChangedSignal:fire(CachedRobuxValue)
end
end
local GetRobuxAmountAsyncTempWidget = nil
function this:GetRobuxAmountAsync()
if CachedRobuxValue then
return CachedRobuxValue
end
UserDataModule.GetLocalUserIdAsync()
spawn(function()
wait(1)
if CachedRobuxValue == nil and GetRobuxAmountAsyncTempWidget == nil then
GetRobuxAmountAsyncTempWidget = LoadingWidget({Parent = RobuxBalanceValue, Size = UDim2.new(0,50,0,50), Position = UDim2.new(1,75,0,25)}, {function() while CachedRobuxValue == nil do wait() end end})
GetRobuxAmountAsyncTempWidget:AwaitFinished()
if GetRobuxAmountAsyncTempWidget then
GetRobuxAmountAsyncTempWidget:Cleanup()
end
GetRobuxAmountAsyncTempWidget = nil
end
end)
self:RefreshRobuxAmountAsync()
return CachedRobuxValue
end
function this:Destroy()
destroyed = true
RobuxBalanceTitle.Parent = nil
self.RobuxChanged = nil
if robuxAmountChangedLoader then
robuxAmountChangedLoader:Cleanup()
robuxAmountChangedLoader = nil
end
RobuxChangedConn = Utility.DisconnectEvent(RobuxChangedConn)
internalPlatformRobuxChangedConn = Utility.DisconnectEvent(internalPlatformRobuxChangedConn)
internalTotalRobuxChangedConn = Utility.DisconnectEvent(internalTotalRobuxChangedConn)
EventHub:removeEventListener(EventHub.Notifications["RobuxCatalogPurchaseInitiated"], myEventId)
end
function this:GetGuiObject()
return RobuxBalanceTitle
end
internalPlatformRobuxChangedConn = InternalPlatformRobuxAmountChangedSignal:connect(
function(newPlatformRobux)
if newPlatformRobux ~= CachedRobuxValue then
CachedRobuxValue = newPlatformRobux
UpdateBalanceText()
this.RobuxChanged:fire(CachedRobuxValue)
end
end)
internalTotalRobuxChangedConn = InternalTotalRobuxAmountChangedSignal:connect(
function(newTotalRobux)
if newTotalRobux ~= CachedTotalRobuxValue then
CachedTotalRobuxValue = newTotalRobux
this.RobuxChanged:fire(CachedRobuxValue)
end
end)
spawn(function()
this:GetRobuxAmountAsync()
end)
return this
end
return CreateCurrencyWidget
@@ -0,0 +1,187 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = Game:GetService("CoreGui")
local GuiService = game:GetService('GuiService')
local PlayersService = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SetAccountCredentialsScreen = require(Modules:FindFirstChild('SetAccountCredentialsScreen'))
local SignInScreen = require(Modules:FindFirstChild('SignInScreen'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ANY_KEY_CODES =
{
[Enum.KeyCode.ButtonA] = true;
-- [Enum.KeyCode.ButtonB] = true;
[Enum.KeyCode.ButtonX] = true;
[Enum.KeyCode.ButtonY] = true;
[Enum.KeyCode.ButtonStart] = true;
[Enum.KeyCode.ButtonSelect] = true;
[Enum.KeyCode.ButtonL1] = true;
[Enum.KeyCode.ButtonR1] = true;
}
local GAMEPAD_INPUT_TYPES =
{
[Enum.UserInputType.Gamepad1] = true;
[Enum.UserInputType.Gamepad2] = true;
[Enum.UserInputType.Gamepad3] = true;
[Enum.UserInputType.Gamepad4] = true;
}
local function CreateHomePane(parent)
local this = {}
local AnyButtonBeganConnection, AnyButtonEndedConnection = nil
local EngagementScreenContainer = Utility.Create'Frame'
{
Name = 'EngagementScreen';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Parent = parent;
}
local RobloxLogo = Utility.Create'ImageLabel'
{
Name = 'RobloxLogo';
BackgroundTransparency = 1;
Size = UDim2.new(0, 594, 0, 199);
Image = 'rbxasset://textures/ui/Shell/Icons/ROBLOXSplashLogo.png';
Parent = EngagementScreenContainer;
}
Utility.CalculateAnchor(RobloxLogo, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local AnyButtonHint = Utility.Create'TextLabel'
{
Name = 'AnyButtonHint';
Text = Strings:LocalizedString('EngagementScreenHint');
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
Size = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
Parent = RobloxLogo;
};
Utility.CalculateAnchor(AnyButtonHint, UDim2.new(0.5,0,0,415), Utility.Enum.Anchor.Center)
local function beginAuthenticationAsync(gamePad)
local authResult = nil
local hasRobloxCredentialsResult = nil
local function auth()
authResult = AccountManager:BeginAuthenticationAsync(gamePad)
if not authResult then
print("beginAuthenticationAsync() failed because", result)
authResult = AccountManager.AuthResults.Error
end
if authResult == AccountManager.AuthResults.Success then
hasRobloxCredentialsResult = AccountManager:HasRobloxCredentialsAsync()
end
end
local loader = LoadingWidget(
{ Parent = RobloxLogo, Position = UDim2.new(0.5, 0, 0, 415) }, { auth })
loader:AwaitFinished()
loader:Cleanup()
loader = nil
print("beginAuthenticationAsync has completed with code:", authResult)
if authResult == AccountManager.AuthResults.Success then
if hasRobloxCredentialsResult == AccountManager.AuthResults.Success then
EventHub:dispatchEvent(EventHub.Notifications["AuthenticationSuccess"])
elseif hasRobloxCredentialsResult == AccountManager.AuthResults.UsernamePasswordNotSet then
local setAccountCredentialsScreen = SetAccountCredentialsScreen(Strings:LocalizedString("SetCredentialsTitle"),
Strings:LocalizedString("SetCredentialsPhrase"), Strings:LocalizedString("SetCredentialsWord"))
setAccountCredentialsScreen:SetParent(EngagementScreenContainer.Parent)
ScreenManager:OpenScreen(setAccountCredentialsScreen, true)
else
local err = Errors.Authentication[hasRobloxCredentialsResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
elseif authResult == AccountManager.AuthResults.AccountUnlinked then
local signInScreen = SignInScreen()
signInScreen:SetParent(EngagementScreenContainer.Parent)
ScreenManager:OpenScreen(signInScreen, true)
else
local err = Errors.Authentication[authResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
end
local function onAnyButtonPressed(gamePad)
AnyButtonBeganConnection = Utility.DisconnectEvent(AnyButtonBeganConnection)
AnyButtonEndedConnection = Utility.DisconnectEvent(AnyButtonEndedConnection)
AnyButtonHint.TextColor3 = GlobalSettings.WhiteTextColor
Utility.PropertyTweener(AnyButtonHint, 'TextTransparency', 0, 1, 0.25, Utility.EaseOutQuad, true,
function()
beginAuthenticationAsync(gamePad)
end)
end
function this:Show()
EngagementScreenContainer.Visible = true
end
function this:Hide()
EngagementScreenContainer.Visible = false
end
function this:Focus()
AnyButtonHint.TextColor3 = GlobalSettings.WhiteTextColor
AnyButtonHint.TextTransparency = 0
Utility.DisconnectEvent(AnyButtonBeganConnection)
local anyButtonDown = {}
AnyButtonBeganConnection = UserInputService.InputBegan:connect(function(inputObject)
if GAMEPAD_INPUT_TYPES[inputObject.UserInputType] then
if ANY_KEY_CODES[inputObject.KeyCode] then
AnyButtonHint.TextColor3 = GlobalSettings.GreyTextColor
anyButtonDown[inputObject.KeyCode] = true
end
end
end)
Utility.DisconnectEvent(AnyButtonEndedConnection)
local isAuthenticating = false
AnyButtonEndedConnection = UserInputService.InputEnded:connect(function(inputObject)
if isAuthenticating then return end
isAuthenticating = true
if GAMEPAD_INPUT_TYPES[inputObject.UserInputType] then
if ANY_KEY_CODES[inputObject.KeyCode] and anyButtonDown[inputObject.KeyCode] == true then
SoundManager:Play('ButtonPress')
onAnyButtonPressed(inputObject.UserInputType)
end
end
isAuthenticating = false
anyButtonDown[inputObject.KeyCode] = false
end)
end
function this:RemoveFocus()
AnyButtonBeganConnection = Utility.DisconnectEvent(AnyButtonBeganConnection)
AnyButtonEndedConnection = Utility.DisconnectEvent(AnyButtonEndedConnection)
end
function this:SetParent(newParent)
EngagementScreenContainer.Parent = newParent
end
return this
end
return CreateHomePane
@@ -0,0 +1,119 @@
--[[
// ErrorOverlay.lau
// Creates and error overlay
// NOTE: Right now error and alerts look the same, so we're
// using the same module to make both. If in the future this
// changes, we'll need to move alert to it's own module.
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local createErrorOverlay = function(errorType, isAlert)
if not errorType then
return
end
local this = BaseOverlay()
local title = errorType.Title
local message = errorType.Msg
local errorCode = errorType.Code
local iconImage = Utility.Create'ImageLabel'
{
Name = "IconImage";
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex;
}
iconImage.Image = isAlert and 'rbxasset://textures/ui/Shell/Icons/AlertIcon.png' or
'rbxasset://textures/ui/Shell/Icons/ErrorIconLargeCopy@1080.png'
iconImage.Size = isAlert and UDim2.new(0, 416, 0, 416) or UDim2.new(0, 321, 0, 264)
Utility.CalculateAnchor(iconImage, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
this:SetImage(iconImage)
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, this.RightAlign, 0, 136);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = title;
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local descriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 762, 0, 304);
Position = UDim2.new(0, this.RightAlign, 0, titleText.Position.Y.Offset + 62);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = message;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
if errorCode and not isAlert then
descriptionText.Text = string.format(Strings:LocalizedString('ErrorMessageAndCodePrase'), message, errorCode)
end
local okButton = Utility.Create'TextButton'
{
Name = "OkButton";
Size = UDim2.new(0, 320, 0, 66);
Position = UDim2.new(0, this.RightAlign, 1, -100 - 66);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(Strings:LocalizedString("OkWord"));
ZIndex = this.BaseZIndex;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
function this:GetPriority()
return GlobalSettings.ElevatedPriority
end
--[[ Input Events ]]--
okButton.MouseButton1Click:connect(function()
this:Close()
end)
local baseFocus = this.Focus
function this:Focus()
baseFocus(this)
GuiService.SelectedCoreObject = okButton
end
function this:GetOverlaySound()
return 'Error'
end
return this
end
return createErrorOverlay
@@ -0,0 +1,122 @@
--[[
// Errors.lua
// Global error codes
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Errors =
{
Default = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("DefaultErrorPhrase"), Code = 0 };
GameJoin =
{
-- index mapped to error code returned from c++
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("AlreadyRunningPhrase"), Code = 101 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("WebServerConnectFailPhrase"), Code = 102 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("AccessDeniedByWeb"), Code = 103 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("InstanceNotFound"), Code = 104 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("GameFullPhrase"), Code = 105 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("FollowUserFailed"), Code = 106 };
{ Title = Strings:LocalizedString("UnableToJoinTitle"), Msg = Strings:LocalizedString("DefaultJoinFailPhrase"), Code = 107 };
};
Vote =
{
FloodCheckThresholdMet = { Title = Strings:LocalizedString("CannotVoteTitle"), Msg = Strings:LocalizedString("VoteFloodPhrase"), Code = 201 };
PlayGame = { Title = Strings:LocalizedString("CannotVoteTitle"), Msg = Strings:LocalizedString("VotePlayGamePhrase"), Code = 202 };
};
Favorite =
{
Failed = { Title = Strings:LocalizedString("CannotFavoriteTitle"), Msg = Strings:LocalizedString("DefaultErrorPhrase"), Code = 301 };
FloodCheck = { Title = Strings:LocalizedString("CannotFavoriteTitle"), Msg = Strings:LocalizedString("FavoriteFloodPhrase"), Code = 302 };
};
Test =
{
CannotJoinGame = { Title = "An Error Occured", Msg = "Cannot join games from studio.", Code = 401 };
StillInDev = { Title = "An Error Occured", Msg = "This feature is still in development.", Code = 402 };
FeatureNotAvailableInStudio = { Title = "An Error Occured", Msg = "This feature is not available in Roblox Studio.", Code = 403 };
};
PackageEquip =
{
Default = { Title = Strings:LocalizedString("UnableToEquipTitle"), Msg = Strings:LocalizedString("UnableToEquipPhrase"), Code = 501 };
};
OutfitEquip =
{
Default = { Title = Strings:LocalizedString("UnableToWearOufitTitle"), Msg = Strings:LocalizedString("UnableToWearOufitPhrase"), Code = 601 };
};
PackagePurchase =
{
{ Title = Strings:LocalizedString("UnableToDoPurchaseTitle"), Msg = Strings:LocalizedString("UnableToDoPurchasePhrase"), Code = 701 };
};
RobuxPurchase =
{
{ Title = Strings:LocalizedString("UnableToDoRobuxPurchaseTitle"), Msg = Strings:LocalizedString("UnableToDoRobuxPurchasePhrase"), Code = 801 };
};
Authentication =
{
-- index mapped to int error code from c++
[-1] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthErrorPhrase"), Code = 901 };
-- ["0"]; This is success
[1] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthInProgressPhrase"), Code = 902 };
[2] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthAccountUnlinkedPhrase"), Code = 903 };
[3] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthMissingGamePadPhrase"), Code = 904 };
[4] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthNoUserDetectedPhrase"), Code = 905 };
[5] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("AuthHttpErrorDetected"), Code = 906 };
[6] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkSignUpDisabled"), Code = 907 };
[7] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkFlooded"), Code = 908 };
[8] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkLeaseLocked"), Code = 909 };
[9] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkAccountLinkingDisabled"), Code = 910 };
[10] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkInvalidRobloxUser"), Code = 911 };
[11] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkRobloxUserAlreadyLinked"), Code = 912 };
[12] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkXboxUserAlreadyLinked"), Code = 913 };
[13] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkIllegalChildAccountLinking"), Code = 914 };
[14] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkInvalidPassword"), Code = 915 };
[15] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkUsernamePasswordNotSet"), Code = 916 };
[16] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkUsernameAlreadyTaken"), Code = 917 };
[17] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("LinkInvalidCredentials"), Code = 918 };
};
Reauthentication =
{
-- index mapped to int error code from c++, you must index into this with a string
[0] = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("ReauthUnknownPhrase"), Code = 1001 };
[1] = { Title = Strings:LocalizedString("ReauthSignedOutTitle"), Msg = Strings:LocalizedString("ReauthSignedOutPhrase"), Code = 1002 };
[2] = { Title = Strings:LocalizedString("ReauthRemovedTitle"), Msg = Strings:LocalizedString("ReauthRemovedPhrase"), Code = 1003 };
[3] = { Title = Strings:LocalizedString("ReauthSignedOutTitle"), Msg = Strings:LocalizedString("ReauthInvalidSessionPhrase"), Code = 1004 };
[4] = { Title = Strings:LocalizedString("ReauthUnlinkTitle"), Msg = Strings:LocalizedString("ReauthUnlinkPhrase"), Code = 1005 };
[5] = { Title = Strings:LocalizedString("ReauthRemovedTitle"), Msg = Strings:LocalizedString("ReauthRemovedPhrase"), Code = 1006 };
[6] = { Title = Strings:LocalizedString("ReauthRemovedTitle"), Msg = Strings:LocalizedString("ReauthRemovedPhrase"), Code = 1007 };
[7] = { Title = Strings:LocalizedString("ReauthRemovedTitle"), Msg = Strings:LocalizedString("ReauthRemovedPhrase"), Code = 1008 };
};
SignIn =
{
["Invalid Username"] = { Title = Strings:LocalizedString("InvalidUsernameTitle"), Msg = Strings:LocalizedString("InvalidUsernamePhrase"), Code = 1101 };
InvalidPassword = { Title = Strings:LocalizedString("InvalidPasswordTitle"), Msg = Strings:LocalizedString("InvalidPasswordPhrase"), Code = 1102 };
["Already Taken"] = { Title = Strings:LocalizedString("AlreadyTakenTitle"), Msg = Strings:LocalizedString("AlreadyTakenPhrase"), Code = 1103 };
["Invalid Characters Used"] = { Title = Strings:LocalizedString("InvalidUsernameTitle"), Msg = Strings:LocalizedString("InvalidCharactersUsedPhrase"), Code = 1104 };
["Username Cannot Contain Spaces"] = { Title = Strings:LocalizedString("InvalidUsernameTitle"), Msg = Strings:LocalizedString("UsernameCannotContainSpacesPhrase"), Code = 1105 };
NoUsernameEntered = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("NoUsernameEnteredPhrase"), Code = 1106 };
NoUsernameOrPasswordEntered = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("NoUsernameOrPasswordEnteredPhrase"), Code = 1107 };
["ConnectionFailed"] = { Title = Strings:LocalizedString("AuthenticationErrorTitle"), Msg = Strings:LocalizedString("WebServerConnectFailPhrase"), Code = 1108 };
};
PlatformError =
{
PopupPartyUI = { Title = Strings:LocalizedString("ErrorOccurredTitle"), Msg = Strings:LocalizedString("PopupPartyUIErrorPhrase"), Code = 1201 };
};
}
return Errors
@@ -0,0 +1,174 @@
-- Written by Kyler Mulherin, Copyright ROBLOX 2015
local listeners = {}
--listeners is a table that holds arrays of listener objects
--Ex - listeners["login"] = { Listener , Listener, Listener }
local function createListener(idString, callbackFunction)
local Listener = { id = idString , callback = callbackFunction };
return Listener;
end
--Initialize all the functions for the EventHub
local EventHub = {}
do
function EventHub:addEventListener(eventString, objectIDString, callbackFunction)
--print ('Adding Listener with ID : ' .. objectIDString)
if (listeners[eventString] == nil) then
listeners[eventString] = {}
end
table.insert(listeners[eventString], createListener(objectIDString, callbackFunction))
end
function EventHub:removeEventListener(eventString, objectIDString)
--print ('Removing Listener with ID : ' .. objectIDString)
if (listeners[eventString] == nil) then return end
--iterate through the listeners for an event string, remove all of the listeners with the provided objectIDString
for key, value in ipairs(listeners[eventString]) do
local listener = value
if (listener ~= nil) then
if (listener.id == objectIDString) then
--print ('-Removing listener with id : ' .. listener.id)
table.remove(listeners[eventString], key)
end
end
end
end
function EventHub:removeCallbackFromEvent(eventString, objectIDString, callbackFunction)
--NOTE- Will not work with anonymous functions
--print ('Removing Listener with ID : ' .. objectIDString)
if (listeners[eventString] == nil) then return end
--iterate through the listeners for an event string, remove the one with the provided objectIDString and callback function
for key, value in ipairs(listeners[eventString]) do
local listener = value
if (listener ~= nil) then
if (listener.id == objectIDString) and (listener.callback == callbackFunction) then
table.remove(listeners[eventString], key)
break
end
end
end
end
function EventHub:dispatchEvent(eventString, ...)
--print ('Dispatching Event : ' .. eventString .. ' with data : ' .. tostring(data))
if (listeners[eventString] == nil) then
return
end
--loop through all the listeners and call the callback function
for key, value in ipairs(listeners[eventString]) do
value.callback(...)
end
end
--A comprehensive list of notification strings to read from
EventHub.Notifications = {
-- Authentication
AuthenticationSuccess = "rbxNotificationAuthenticationSuccess";
GameJoin = "rbxNotificationGameJoin";
-- Game Notifications
OpenGames = "rbxNotificationOpenGames";
OpenGameDetail = "rbxNotificationOpenGameDetail";
OpenGameGenre = "rbxNotificationOpenGameGenre";
OpenBadgeScreen = "rbxNotificationOpenBadgeScreen";
-- Unlink Account Notification
UnlinkAccountConfirmation = "rbxNotificationUnlinkAccountConfirmation";
-- Overscan Notifications
OpenOverscanScreen = "rbxNotificationOpenOverscanScreen";
-- Engagement Screen Notifcations
OpenEngagementScreen = "rbxNotificationOpenEngagementScreen";
--Social Notifications
OpenSocialScreen = "rbxNotificationOpenSocialScreen";
--Settings Notifications
OpenSettingsScreen = "rbxNotificationOpenSettingsScreen";
--Avatar Screen Notifications
NavigateToEquippedAvatar = "rbxNotificationNavigateToEquippedAvatar";
-- Robux Screen Notification
NavigateToRobuxScreen = "rbxNotificationNavigateToRobuxScreen";
RobuxCatalogPurchaseInitiated = "rbxRobuxCatalogPurchaseInitiated";
--
-- Achievement Related Events
TestXButtonPressed = "rbxTestXButtonPressed";
DonnedDifferentPackage = "rbxDonnedDifferentPackage";
VotedOnPlace = "rbxVotedOnPlace";
-- Hero Stats Related Events
AvatarEquipped = "rbxAvatarEquipped";
-- JoinedParty = "rbxJoinedParty";
AvatarEquipBegin = "rbxAvatarEquipBegin";
DonnedDifferentOutfit = "rbxDonnedDifferentOutfit";
};
end
return EventHub;
--print 'Event Hub initialized'
-- [[ TESTING STUFF - include somewhere the EventHub has been initialized ]]--
--[[
-- TEST #1 -- removing entire listeners from events
print '\nAdding Event Listeners...';
EventHub:addEventListener( "test0", "testID1", function (data) print('Test 1 Received test0 : ' .. data) end);
EventHub:addEventListener( "test0", "testID2", function (data) print('Test 2 Received test0 : ' .. data) end);
EventHub:addEventListener( "test0", "testID3", function (data) print('Test 3 Received test0 : ' .. data) end);
EventHub:addEventListener( "test0", "testID4", function (data) print('Test 4 Received test0 : ' .. data) end);
print '\nDispatching Events...';
EventHub:dispatchEvent("test0", "var");
--SHOULD SEE ALL 4 LISTENERS REPORT
print '\nDispatching Events...';
EventHub:dispatchEvent("test0", "var2");
print '\nRemoving TestID2 and 3s Listeners...';
EventHub:removeEventListener("test0", "testID2");
EventHub:removeEventListener("test0", "testID3");
print '\nDispatching Events...';
EventHub:dispatchEvent("test0", "var3");
--SHOULD ONLY SEE testID1 AND testID4 REPORT
-- TEST #2 -- selective removal of listeners
print '\nAdding Generic Function Event Listeners...';
local function test1(data) print('Generic function 1. Received test1 : ' .. data) end;
local function test2(data) print('Generic function 2. Received test1 : ' .. data) end;
local testFunc1 = test1;
local testFunc2 = test2;
EventHub:addEventListener( "test1", "testID1", testFunc1);
EventHub:addEventListener( "test1", "testID1", testFunc2);
EventHub:addEventListener( "test1", "testID2", testFunc1);
print '\nDispatching Events...';
EventHub:dispatchEvent("test1", "foo");
--SHOULD SEE 3 REPORTS
print '\nRemoving Generic Function 1 Callback of testID1';
EventHub:removeCallbackFromEvent("test1", "testID1", testFunc1);
print '\nDispatching Events...';
EventHub:dispatchEvent("test1", "foo3");
--SHOULD SEE THAT testID1 NO LONGER REPORTS FROM GENERIC FUNCTION 1
]]--
@@ -0,0 +1,157 @@
--[[
// FriendPresenceItem.lua
// Creates a friend activity gui item to be used with a ScrollingGrid
// for friends social status
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local FAIL_IMG = 'rbxasset://textures/ui/Shell/Icons/DefaultProfileIcon.png'
local function FriendPresenceItem(size, idStr)
local this = {}
local TEXT_OFFSET = 12
local container = Utility.Create'ImageButton'
{
Name = idStr;
Size = size;
Position = UDim2.new(0, 0, 0, 0);
BackgroundColor3 = GlobalSettings.WhiteTextColor;
BorderSizePixel = 0;
BackgroundTransparency = 1;
AutoButtonColor = false;
SoundManager:CreateSound('MoveSelection');
}
container.SelectionGained:connect(function()
local startTween = Utility.PropertyTweener(container, "BackgroundTransparency", 1, GlobalSettings.AvatarBoxBackgroundSelectedTransparency, 0,
Utility.EaseInOutQuad, true, nil)
end)
container.SelectionLost:connect(function()
container.BackgroundTransparency = 1
end)
local avatarImage = Utility.Create'ImageLabel'
{
Name = "AvatarImage";
Image = '';
Size = UDim2.new(0, 104, 0, 104);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 0;
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.CharacterBackgroundColor;
ZIndex = 2;
Parent = container;
AssetManager.CreateShadow(1);
}
local nameLabel = Utility.Create'TextLabel'
{
Name = "NameLabel";
Text = "";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, avatarImage.Size.X.Offset + TEXT_OFFSET, 0, 32);
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
BackgroundTransparency = 1;
Parent = container;
}
local presenceStatusImage = Utility.Create'ImageLabel'
{
Name = "PresenceStatusImage";
BackgroundTransparency = 1;
Parent = container;
}
AssetManager.LocalImage(presenceStatusImage,
'rbxasset://textures/ui/Shell/Icons/OnlineStatusIcon', {['720'] = UDim2.new(0,13,0,13); ['1080'] = UDim2.new(0,19,0,20);})
presenceStatusImage.Position = UDim2.new(0, nameLabel.Position.X.Offset, 0,
nameLabel.Position.Y.Offset + 36 - (presenceStatusImage.Size.Y.Offset/2) + 1)
local presenceLabel = Utility.Create'TextLabel'
{
Name = "PresenceLabel";
Text = "";
Size = UDim2.new(0,
container.Size.X.Offset - presenceStatusImage.Position.X.Offset - presenceStatusImage.Size.X.Offset - 12, 0, 32);
Position = UDim2.new(0, presenceStatusImage.Position.X.Offset + presenceStatusImage.Size.X.Offset + 12, 0,
presenceStatusImage.Position.Y.Offset + presenceStatusImage.Size.Y.Offset / 2 - 16);
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.LightGreyTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.DescriptionSize;
BackgroundTransparency = 1;
ClipsDescendants = true;
Parent = container;
}
local lineBreak = Utility.Create'Frame'
{
Name = "Break";
Size = UDim2.new(1, -avatarImage.Size.X.Offset - TEXT_OFFSET, 0, 2);
Position = UDim2.new(0, avatarImage.Size.X.Offset + TEXT_OFFSET, 1, -2);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.LineBreakColor;
Parent = container;
}
local failImage = Utility.Create'ImageLabel'
{
Name = "FailImage";
Size = UDim2.new(0.5, 0, 0.5, 0);
Position = UDim2.new(0.25, 0, 0.25, 0);
BackgroundTransparency = 1;
Image = FAIL_IMG;
ZIndex = 2;
}
function this:GetContainer()
return container
end
function this:SetAvatarImage(userId)
local loader = ThumbnailLoader:Create(avatarImage, userId,
ThumbnailLoader.Sizes.Small, ThumbnailLoader.AssetType.Avatar)
spawn(function()
if not loader:LoadAsync(true, false) then
failImage.Parent = avatarImage
else
failImage.Parent = nil
end
end)
end
function this:GetNameText()
return nameLabel.Text
end
function this:SetNameText(name)
nameLabel.Text = name
end
function this:SetPresence(str, isInRobloxGame)
presenceLabel.Text = str
presenceStatusImage.ImageColor3 = isInRobloxGame and GlobalSettings.GreenTextColor
or GlobalSettings.GreySelectedButtonColor
end
function this:SetLastActivityText(str)
lastActivityLabel.Text = str
end
function this:Destroy()
container:Destroy()
this = nil
end
return this
end
return FriendPresenceItem
@@ -0,0 +1,286 @@
--[[
// FriendsData.lua
// Caches the current friends pagination to used by anyone in the app
// polls every POLL_DELAY and gets the latest pagination
// TODO:
Need polling to update friends. How are we going to handle all the cases
like the person you're selecting going offline, etc..
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Players = game:GetService('Players')
local HttpService = game:GetService('HttpService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Http = require(Modules:FindFirstChild('Http'))
local Utility = require(Modules:FindFirstChild('Utility'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local UserData = require(Modules:FindFirstChild('UserData'))
local SortData = require(Modules:FindFirstChild('SortData'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
-- NOTE: This is just required for fixing Usernames in auto-generatd games
local GameData = require(Modules:FindFirstChild('GameData'))
local ConvertMyPlaceNameInXboxAppFlag = Utility.IsFastFlagEnabled("ConvertMyPlaceNameInXboxApp")
local FriendsData = {}
local pollDelay = Utility.GetFastVariable("XboxFriendsPolling")
if pollDelay then
pollDelay = tonumber(pollDelay)
end
local POLL_DELAY = pollDelay or 30
local STATUS = {
UNKNOWN = "Unknown";
ONLINE = "Online";
OFFLINE = "Offline";
AWAY = "Away";
}
local isOnlineFriendsPolling = false
local myCurrentFriendsData = nil
local updateEventCns = {}
local function filterXboxFriends(friendsData)
local titleId = PlatformService and tostring(PlatformService:GetTitleId()) or ''
local onlineRobloxFriendsData = {}
local onlineRobloxFriendsUserIds = {}
local onlineFriendsData = {}
-- filter into two list, those online in roblox and those online
-- for those online, also get their roblox userId
for i = 1, #friendsData do
local data = friendsData[i]
if data["status"] and data["status"] == STATUS.ONLINE then
if data["rich"] then
-- get rich presence table, last entry is most recent activity from user
local richTbl = data["rich"]
richTbl = richTbl[#richTbl]
if richTbl["titleId"] == titleId then
table.insert(onlineRobloxFriendsData, data)
table.insert(onlineRobloxFriendsUserIds, data["robloxuid"])
else
table.insert(onlineFriendsData, data)
end
end
end
end
-- now get roblox friends presence in roblox
local robloxPresence = {}
if #onlineRobloxFriendsUserIds > 0 then
local jsonTable = {}
jsonTable["userIds"] = onlineRobloxFriendsUserIds
local jsonPostBody = HttpService:JSONEncode(jsonTable)
robloxPresence = Http.GetUsersOnlinePresenceAsync(jsonPostBody)
if robloxPresence and robloxPresence["UserPresences"] then
robloxPresence = robloxPresence["UserPresences"]
end
end
-- now append roblox presence data to each users data
for i = 1, #onlineRobloxFriendsData do
if robloxPresence[i] then
local data = onlineRobloxFriendsData[i]
-- make sure we have the right person
for j = 1, #robloxPresence do
local rbxPresenceData = robloxPresence[j]
local rbxUserId = rbxPresenceData["VisitorId"]
if rbxUserId == data["robloxuid"] then
if rbxPresenceData["IsOnline"] == true then
local placeId = rbxPresenceData["PlaceId"]
local lastLocation = rbxPresenceData["LastLocation"]
-- If the lastLocation for a user is some user place with a GeneratedUsername in it
-- then replace it with the actual creator name!
if ConvertMyPlaceNameInXboxAppFlag and placeId and lastLocation and GameData:ExtractGeneratedUsername(lastLocation) then
local gameCreator = GameData:GetGameCreatorAsync(placeId)
if gameCreator then
lastLocation = GameData:GetFilteredGameName(lastLocation, gameCreator)
end
end
-- If the user is not in a featured game, then hide their presence
if placeId and not SortData:IsFeaturedGameAsync(placeId) then
data["IsPrivateSession"] = true
lastLocation = Strings:LocalizedString("PrivateSessionPhrase")
end
data["PlaceId"] = placeId
data["LastLocation"] = lastLocation
end
-- remove from list and gtfo
table.remove(robloxPresence, j)
break
end
end
end
end
-- now sort those in roblox
table.sort(onlineRobloxFriendsData, function(a, b)
if a["PlaceId"] and b["PlaceId"] then
return a["display"] < b["display"]
end
if a["PlaceId"] then
return true
end
if b["PlaceId"] then
return false
end
return a["display"] < b["display"]
end)
-- now sort all other friends
table.sort(onlineFriendsData, function(a, b)
return a["display"] < b["display"]
end)
-- now concat tables
for i = 1, #onlineFriendsData do
onlineRobloxFriendsData[#onlineRobloxFriendsData + 1] = onlineFriendsData[i]
end
return onlineRobloxFriendsData
end
--[[
// Returns table with an array with the key friends
// Keys
// xuid - number, xbox user id
// gamertage - string, users gamertag
// display - string, users display name (depends on user settings, could be same as gamertag)
// robloxuid - number, users roblox userId
// status - string (Online, Away, Offline, Unknown)
// rich - array of rich presence, might currently be empty?
// timestamp - number, UTC timestamp of record
// device - string, XboxOne, Windows8, etc.
// title - string, of game
// playing - boolean, are they playing the game
// presence - string, rich presence string from that title
]]
local function fetchXboxFriendsAsync()
local success, result = pcall(function()
if PlatformService then
return PlatformService:BeginFetchFriends(Enum.UserInputType.Gamepad1)
end
end)
if success then
return HttpService:JSONDecode(result)
else
print("fetchXboxFriends failed because", result)
end
end
local function getOnlineFriends()
if UserSettings().GameSettings:InStudioMode() then
-- Roblox Friends - leaving this in for testing purposes in studio
local result = Http.GetOnlineFriendsAsync()
if not result then
-- TODO: Error code
return nil
end
--
local myOnlineFriends = {}
for i = 1, #result do
local data = result[i]
local friend = {
Name = Players:GetNameFromUserIdAsync(data["VisitorId"]);
UserId = data["VisitorId"];
LastLocation = data["LastLocation"];
PlaceId = data["PlaceId"];
LocationType = data["LocationType"];
GameId = data["GameId"];
}
table.insert(myOnlineFriends, friend)
end
local function sortFunc(a, b)
if a.LocationType == b.LocationType then
return a.Name:lower() < b.Name:lower()
end
return a.LocationType > b.LocationType
end
table.sort(myOnlineFriends, sortFunc)
return myOnlineFriends
elseif game:GetService('UserInputService'):GetPlatform() == Enum.Platform.XBoxOne then
-- Xbox Friends
local myXboxFriends = fetchXboxFriendsAsync()
local myOnlineFriends = {}
if myXboxFriends then
myXboxFriends = myXboxFriends["friends"]
myOnlineFriends = filterXboxFriends(myXboxFriends)
end
return myOnlineFriends
end
end
--[[ Public API ]]--
FriendsData.OnFriendsDataUpdated = Utility.Signal()
local isFetchingFriends = false
function FriendsData.GetOnlineFriendsAsync()
-- can only make one call into PlatformService:BeginFetchFriends() at a time
while isFetchingFriends do
wait()
end
-- we have current data, this will be updated when polling
if myCurrentFriendsData then
return myCurrentFriendsData
end
isFetchingFriends = true
myCurrentFriendsData = getOnlineFriends()
-- spawn polling on first request
if not isOnlineFriendsPolling then
FriendsData.BeginPolling()
end
isFetchingFriends = false
return myCurrentFriendsData
end
function FriendsData.BeginPolling()
if not isOnlineFriendsPolling then
isOnlineFriendsPolling = true
local requesterId = UserData:GetRbxUserId()
spawn(function()
wait(POLL_DELAY)
while requesterId == UserData:GetRbxUserId() do
myCurrentFriendsData = getOnlineFriends()
FriendsData.OnFriendsDataUpdated:fire(myCurrentFriendsData)
wait(POLL_DELAY)
end
end)
end
end
-- we make connections through this function so we can clean them all up upon
-- clearing the friends data
function FriendsData.ConnectUpdateEvent(cbFunc)
local cn = FriendsData.OnFriendsDataUpdated:connect(cbFunc)
table.insert(updateEventCns, cn)
end
function FriendsData.Reset()
isOnlineFriendsPolling = false
myCurrentFriendsData = nil
for index,cn in pairs(updateEventCns) do
cn = Utility.DisconnectEvent(cn)
updateEventCns[index] = nil
end
print('FriendsData: Cleared last users FriendsData')
end
return FriendsData
@@ -0,0 +1,204 @@
--[[
// FriendsView.lua
// Creates a view for the users friends.
// Handles user input, updating view
TODO:
Connect selected/deselected to change color
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserInputService = game:GetService('UserInputService')
local GuiService = game:GetService('GuiService')
local FriendsData = require(Modules:FindFirstChild('FriendsData'))
local FriendPresenceItem = require(Modules:FindFirstChild('FriendPresenceItem'))
local SideBarModule = require(Modules:FindFirstChild('SideBar'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local GameJoinModule = require(Modules:FindFirstChild('GameJoin'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
-- Array of tables
-- see FriendsData - fetchXboxFriends() for full documentation
local myFriendsData = nil
local FOLLOW_MODE = 2
local SIDE_BAR_ITEMS = {
JoinGame = string.upper(Strings:LocalizedString("JoinGameWord"));
ViewDetails = string.upper(Strings:LocalizedString("ViewGameDetailsWord"));
ViewProfile = string.upper(Strings:LocalizedString("ViewGamerCardWord"));
}
-- side bar is shared between all views
local SideBar = SideBarModule()
local function setPresenceData(item, data)
if UserSettings().GameSettings:InStudioMode() then
item:SetAvatarImage(data.UserId)
item:SetNameText(data.Name)
item:SetPresence(data.LastLocation, data.PlaceId ~= nil)
elseif UserInputService:GetPlatform() == Enum.Platform.XBoxOne then
local rbxuid = data["robloxuid"]
item:SetAvatarImage(rbxuid)
item:SetNameText(data["display"])
if data["PlaceId"] and data["LastLocation"] then
item:SetPresence(data["LastLocation"], true)
elseif data["rich"] then
local richTbl = data["rich"]
local presence = richTbl[#richTbl]
item:SetPresence(presence["title"], false)
end
end
end
-- viewGridContainer - ScrollingGrid
-- friendData - FriendsData
-- sizeConstraint - limit view to this many items
-- updateFunc - function that will be called when FriendData update
local createFriendsView = function(viewGridContainer, friendsData, sizeConstraint, updateFunc)
local this = {}
-- map of userId to presenceItem
local presenceItems = {}
local selectedItemOnFocus = nil
local guiObjectToSortIndex = {}
local presenceItemToData = {}
local count = #friendsData
if sizeConstraint then
count = math.min(count, sizeConstraint)
end
local function connectSideBar(item)
local container = item:GetContainer()
container.MouseButton1Click:connect(function()
local data = presenceItemToData[item]
if data then
-- rebuild side bar based on current data
SideBar:RemoveAllItems()
local inGame = data["PlaceId"] ~= nil
if inGame and not data["IsPrivateSession"] then
SideBar:AddItem(SIDE_BAR_ITEMS.JoinGame, function()
GameJoinModule:StartGame(GameJoinModule.JoinType.Follow, data["robloxuid"])
end)
SideBar:AddItem(SIDE_BAR_ITEMS.ViewDetails, function()
-- pass nil for iconId, gameDetail will fetch
EventHub:dispatchEvent(EventHub.Notifications["OpenGameDetail"], data["PlaceId"], data["LastLocation"], nil)
end)
end
SideBar:AddItem(SIDE_BAR_ITEMS.ViewProfile, function()
if PlatformService and data["xuid"] then
local success, result = pcall(function()
PlatformService:PopupProfileUI(Enum.UserInputType.Gamepad1, data["xuid"])
end)
-- NOTE: This will try to pop up the xbox system gamer card, failure will be handled
-- by the xbox.
if not success then
print("PlatformService:PopupProfileUI failed because,", result)
end
end
end)
ScreenManager:OpenScreen(SideBar, false)
else
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.Default), false)
end
end)
end
-- initialize view
for i = 1, count do
local data = friendsData[i]
if data then
local idStr = tostring(data.UserId or data["robloxuid"])
local presenceItem = FriendPresenceItem(UDim2.new(0, 446, 0, 114), idStr)
presenceItems[idStr] = presenceItem
presenceItemToData[presenceItem] = data
viewGridContainer:AddItem(presenceItem:GetContainer())
setPresenceData(presenceItem, data)
connectSideBar(presenceItem)
if not selectedItemOnFocus then
selectedItemOnFocus = presenceItem:GetContainer()
end
guiObjectToSortIndex[presenceItem:GetContainer()] = i
end
end
local function onFriendsUpdated(newFriendsData)
local size = #newFriendsData
if sizeConstraint then
size = math.min(size, sizeConstraint)
end
-- map of valid userIds to bool
local validEntries = {}
-- a,b are guiObjects
local function sortGridItems(a, b)
if guiObjectToSortIndex[a] and guiObjectToSortIndex[b] then
return guiObjectToSortIndex[a] < guiObjectToSortIndex[b]
end
if guiObjectToSortIndex[a] then return true end
if guiObjectToSortIndex[b] then return false end
return a.Name < b.Name
end
-- refresh view
for i = 1, size do
local data = newFriendsData[i]
if data then
local idStr = tostring(data.UserId or data["robloxuid"])
local presenceItem = presenceItems[idStr]
if not presenceItem then
presenceItem = FriendPresenceItem(UDim2.new(0, 446, 0, 114), idStr)
presenceItems[idStr] = presenceItem
viewGridContainer:AddItem(presenceItem:GetContainer())
connectSideBar(presenceItem)
end
presenceItemToData[presenceItem] = data
setPresenceData(presenceItem, data)
validEntries[idStr] = true
guiObjectToSortIndex[presenceItem:GetContainer()] = i
if i == 1 then
selectedItemOnFocus = presenceItem:GetContainer()
end
end
end
-- remove items if needed
for userId,presenceItem in pairs(presenceItems) do
if not validEntries[userId] then
local container = presenceItem:GetContainer()
guiObjectToSortIndex[container] = nil
viewGridContainer:RemoveItem(container)
presenceItems[userId] = nil
presenceItem:Destroy()
presenceItem = nil
end
end
viewGridContainer:SortItems(sortGridItems)
if updateFunc then
updateFunc(#newFriendsData)
end
end
FriendsData.ConnectUpdateEvent(onFriendsUpdated)
function this:GetDefaultFocusItem()
return selectedItemOnFocus
end
return this
end
return createFriendsView
@@ -0,0 +1,125 @@
--[[
// GameCollection.lua
// Used to get a collection of games
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local SortData = require(Modules:FindFirstChild('SortData'))
local UserData = require(Modules:FindFirstChild('UserData'))
local Utility = require(Modules:FindFirstChild('Utility'))
local GameCollection = {}
GameCollection.DefaultSortId = {
Popular = 1;
Featured = 3;
TopEarning = 8;
TopRated = 11;
}
local function createBaseCollection()
local this = {}
function this:GetSortAsync(startIndex, pageSize)
print("GameCollection GetSortAsync() must be implemented by sub class")
end
return this
end
local SortCollections = {}
function GameCollection:GetSort(sortId)
if SortCollections[sortId] then
return SortCollections[sortId]
end
local collection = createBaseCollection()
-- Override
function collection:GetSortAsync(startIndex, pageSize)
local sort = SortData.GetSort(sortId)
local timeFilter = nil
-- top rated is time filtered to show most recent top rated
if sortId == GameCollection.DefaultSortId.TopRated then
timeFilter = 2
end
return sort:GetPageAsync(startIndex, pageSize, timeFilter)
end
SortCollections[sortId] = collection
return collection
end
local UserFavoriteCollection = nil
function GameCollection:GetUserFavorites()
if UserFavoriteCollection then
return UserFavoriteCollection
end
UserFavoriteCollection = createBaseCollection()
-- Override
function UserFavoriteCollection:GetSortAsync(startIndex, pageSize)
local sort = SortData.GetUserFavorites()
return sort:GetPageAsync(startIndex, pageSize)
end
return UserFavoriteCollection
end
local UserRecentCollection = nil
function GameCollection:GetUserRecent()
if UserRecentCollection then
return UserRecentCollection
end
UserRecentCollection = createBaseCollection()
-- Override
function UserRecentCollection:GetSortAsync(startIndex, pageSize)
local sort = SortData.GetUserRecent()
return sort:GetPageAsync(startIndex, pageSize)
end
return UserRecentCollection
end
local UserPlacesCollection = nil
function GameCollection:GetUserPlaces()
if UserPlacesCollection then
return UserPlacesCollection
end
UserPlacesCollection = createBaseCollection()
-- Override
function UserPlacesCollection:GetSortAsync(startIndex, pageSize)
if Utility.IsFastFlagEnabled("XboxPlayMyPlace") then
local userId = UserData:GetRbxUserId()
if userId then
local sort = SortData.GetUserPlaces(userId)
return sort:GetPageAsync(startIndex, pageSize)
end
end
end
return UserPlacesCollection
end
if PlatformService then
PlatformService.UserAccountChanged:connect(function()
SortCollections = {}
UserFavoritCollection = nil
UserRecentCollection = nil
UserPlacesCollection = nil
end)
end
return GameCollection
@@ -0,0 +1,255 @@
--[[
// GameData.lua
// Fetches data for a game to be used to fill out
// the details of that game
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Http = require(Modules:FindFirstChild('Http'))
local Utility = require(Modules:FindFirstChild('Utility'))
local UserData = require(Modules:FindFirstChild('UserData'))
local ConvertMyPlaceNameInXboxAppFlag = Utility.IsFastFlagEnabled("ConvertMyPlaceNameInXboxApp")
local GameData = {}
local gameCreatorCache = {}
function GameData:GetGameCreatorAsync(placeId)
if placeId then
if not gameCreatorCache[placeId] then
local gameDataByPlaceId = self:GetGameDataAsync(placeId)
gameCreatorCache[placeId] = gameDataByPlaceId:GetCreatorName()
end
return gameCreatorCache[placeId]
end
end
function GameData:ExtractGeneratedUsername(gameName)
local tempUsername = string.match(gameName, "^([0-9a-fA-F]+)'s Place$")
if tempUsername and #tempUsername == 32 then
return tempUsername
end
end
-- Fix places that have been made with incorrect temporary usernames
-- creatorName is optional and must be used when querying a game that is
-- not the current user's creation
function GameData:GetFilteredGameName(gameName, creatorName)
if ConvertMyPlaceNameInXboxAppFlag and gameName and type(gameName) == 'string' then
local tempUsername = self:ExtractGeneratedUsername(gameName)
if tempUsername then
local realUsername = creatorName or UserData:GetRobloxName()
if realUsername then
local newGameName = string.gsub(gameName, tempUsername, realUsername, 1)
if newGameName then
return newGameName
end
end
end
end
return gameName
end
function GameData:GetGameDataAsync(placeId)
local this = {}
local result = Http.GetGameDetailsAsync(placeId)
if not result then
print("GameData:GetGameDataAsync() failed to get web response for placeId "..tostring(placeId))
result = {}
end
this.Data = result
--[[ Public API ]]--
function this:GetCreatorName()
return self.Data["Builder"] or ""
end
function this:GetDescription()
return self.Data["Description"] or ""
end
function this:GetIsFavoritedByUser()
return self.Data["IsFavoritedByUser"] or false
end
function this:GetLastUpdated()
return self.Data["Updated"] or ""
end
function this:GetCreationDate()
return self.Data["Created"] or ""
end
function this:GetMaxPlayers()
return self.Data["MaxPlayers"] or 0
end
function this:GetOverridesDefaultAvatar()
return self.Data["OverridesDefaultAvatar"] or false
end
function this:GetCreatorUserId()
return self.Data["BuilderId"]
end
--[[ Async Public API ]]--
function this:GetVoteDataAsync()
local result = Http.GetGameVotesAsync(placeId)
if not result then
print("GameData:GetVoteDataAsync() failed to get web response for placeId "..tostring(placeId))
end
local voteData = {}
local voteTable = result and result["VotingModel"] or nil
if voteTable then
voteData.UpVotes = voteTable["UpVotes"] or 0
voteData.DownVotes = voteTable["DownVotes"] or 0
voteData.UserVote = voteTable["UserVote"] or nil
voteData.CanVote = voteTable["CanVote"] or false
voteData.CantVoteReason = voteTable["ReasonForNotVoteable"] or "PlayGame"
end
return voteData
end
function this:GetGameIconIdAsync()
local iconId = nil
local result = Http.GetGameIconIdAsync(placeId)
if result then
iconId = result["ImageId"]
-- use placeId as backup
if not iconId then
iconId = placeId
end
end
return iconId
end
function this:GetRecommendedGamesAsync()
local result = Http.GetRecommendedGamesAsync(placeId)
if not result then
print("GameData:GetRecommendedGamesAsync() failed to get web response for placeId "..tostring(placeId))
return {}
end
local recommendedGames = {}
for i = 1, #result do
local data = result[i]
if data then
local game = {}
-- Temp fix for fixing game names
game.Name = GameData:GetFilteredGameName(data["GameName"], data["Creator"] and data["Creator"]["CreatorName"])
game.PlaceId = data["PlaceId"]
game.IconId = data["ImageId"]
table.insert(recommendedGames, game)
end
end
return recommendedGames
end
function this:GetThumbnailIdsAsync()
local result = Http.GetGameThumbnailsAsync(placeId)
if not result then
print("GameData:GetThumbnailIdsAsync() failed to get web response for placeId "..tostring(placeId))
return {}
end
local thumbIds = {}
local thumbIdTable = result["thumbnails"]
if thumbIdTable then
for i = 1, #thumbIdTable do
local data = thumbIdTable[i]
-- AssetTypeId of 1 is a Image (33 is a video if can ever play videos)
if data and data["AssetTypeId"] == 1 then
local assetId = data["AssetId"]
if assetId then
table.insert(thumbIds, assetId)
end
end
end
end
return thumbIds
end
function this:GetBadgeDataAsync()
local result = Http.GetGameBadgeDataAsync(placeId)
if not result then
print("GameData:GetBadgeDataAsync() failed to get web response for placeId "..tostring(placeId))
return {}
end
local badgeData = {}
local badgeTable = result["GameBadges"]
if badgeTable then
for i = 1, #badgeTable do
local data = badgeTable[i]
if data then
local badge = {}
badge.Name = data["Name"]
badge.Description = data["Description"]
badge.AssetId = data["BadgeAssetId"]
badge.IsOwned = data["IsOwned"]
badge.Order = i
table.insert(badgeData, badge)
end
end
end
table.sort(badgeData, function(a, b)
if a["IsOwned"] == true and b["IsOwned"] == true then
return a.Order < b.Order
elseif a["IsOwned"] then
return true
elseif b["IsOwned"] then
return false
end
return a.Order < b.Order
end)
return badgeData
end
--[[ Post Public API ]]--
function this:PostFavoriteAsync()
local result = Http.PostFavoriteToggleAsync(placeId)
local success = result and result["success"] == true
if not success then
local reason = "Failed"
-- the floodcheck message is "Whoa. Slow Down.". So if there is a message,
-- let's just say flood check?
if result and result["message"] then
reason = "FloodCheck"
end
return success, reason
else
self.Data["IsFavoritedByUser"] = not self:GetIsFavoritedByUser()
end
return success
end
function this:PostVoteAsync(status)
local result = Http.PostGameVoteAsync(placeId, status)
if not result then
return nil
end
local success = result["Success"] == true
if not success then
return success, result["ModalType"]
end
return success
end
-- Temp fix for fixing game names
if this.Data then
this.Data.Name = GameData:GetFilteredGameName(this.Data.Name, this:GetCreatorName())
end
return this
end
return GameData
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,712 @@
--[[
// GameGenre.lua
// Displays a game genre page for a certain sort
TODO:
Clean up code
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local GameDataModule = require(Modules:FindFirstChild('GameData'))
local SideBarModule = require(Modules:FindFirstChild('SideBar'))
local VoteFrameModule = require(Modules:FindFirstChild('VoteFrame'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local SortCarousel = require(Modules:FindFirstChild('SortCarousel'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GameJoinModule = require(Modules:FindFirstChild('GameJoin'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local GameCollection = require(Modules:FindFirstChild('GameCollection'))
local isUseNewCarouselInXboxAppEnabled = Utility.IsFastFlagEnabled("UseNewCarouselInXboxApp")
local CarouselView = require(Modules:FindFirstChild('CarouselView'))
local CarouselController = require(Modules:FindFirstChild('CarouselController'))
local function CreateGameGenre(sortName, gameCollection)
local this = {}
local inFocus = false
local gameLoadCount = 20
local sideBarSorts = {} -- array or sort names and ids for the side bar
local baseButtonTextColor = GlobalSettings.WhiteTextColor
local selectedButtonTextColor = GlobalSettings.TextSelectedColor
local newGameSelectedCn = nil
local sideBarSelectedCn = nil
local dataModelViewChangedCn = nil
local canJoinGame = true
local returnedFromGame = true
local currentShownCollection = gameCollection
--[[ Top Level Elements ]]--
local GameGenreContainer = Utility.Create'Frame'
{
Name = "GameGenreContainer";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
Visible = false;
Parent = parent;
}
local BackLabel = Utility.Create'ImageLabel'
{
Name = "BackLabel";
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ZIndex = 2;
Parent = GameGenreContainer;
}
AssetManager.LocalImage(BackLabel,
'rbxasset://textures/ui/Shell/Icons/BackIcon', {['720'] = UDim2.new(0,32,0,32); ['1080'] = UDim2.new(0,48,0,48);})
local BackText = Utility.Create'TextLabel'
{
Name = "BackText";
Size = UDim2.new(0, 0, 0, BackLabel.Size.Y.Offset);
Position = UDim2.new(0, BackLabel.Size.X.Offset + 8, 0, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = '';
Parent = GameGenreContainer;
}
local SideBarButton = Utility.Create'ImageButton'
{
Name = "SideBarButton";
Size = UDim2.new(0, 450, 0, 75);
Position = UDim2.new(0, 0, 0, BackLabel.Size.Y.Offset + 60);
BackgroundTransparency = 1;
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.GreySelectionColor;
Parent = GameGenreContainer;
SoundManager:CreateSound('MoveSelection');
}
SideBarButton.NextSelectionRight = SideBarButton
SideBarButton.NextSelectionLeft = SideBarButton
SideBarButton.SelectionGained:connect(function()
Utility.PropertyTweener(SideBarButton, 'BackgroundTransparency', 0, 0, 0, nil, true)
end)
SideBarButton.SelectionLost:connect(function()
Utility.PropertyTweener(SideBarButton, 'BackgroundTransparency', 1, 1, 0, nil, true)
end)
local DropDownImage = Utility.Create'ImageLabel'
{
Name = "DropDownImage";
BackgroundTransparency = 1;
Parent = SideBarButton;
}
AssetManager.LocalImage(DropDownImage,
'rbxasset://textures/ui/Shell/Icons/Dropdown02', {['720'] = UDim2.new(0,29,0,29); ['1080'] = UDim2.new(0,44,0,44);})
DropDownImage.Position = UDim2.new(1, -DropDownImage.Size.X.Offset - 12, 0.5, -DropDownImage.Size.Y.Offset / 2 + 4)
local TitleLabel = Utility.Create'TextLabel'
{
Name = "TitleLabel";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, 18, 0.5, 0);
BackgroundTransparency = 1;
Text = string.upper(sortName);
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
ZIndex = 2;
Parent = SideBarButton;
}
local GameTitleLabel = Utility.Create'TextLabel'
{
Name = "GameTitleLabel";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, 18, 0, SideBarButton.Position.Y.Offset + SideBarButton.Size.Y.Offset + 550);
BackgroundTransparency = 1;
Text = "";
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
Parent = GameGenreContainer;
}
local ThumbsUpImage = Utility.Create'ImageLabel'
{
Name = "ThumbsUpImage";
BackgroundTransparency = 1;
ZIndex = 2;
Visible = false;
Parent = GameGenreContainer;
}
local ThumbsDownImage = Utility.Create'ImageLabel'
{
Name = "ThumbsDownImage";
BackgroundTransparency = 1;
ZIndex = 2;
Visible = false;
Parent = GameGenreContainer;
}
AssetManager.LocalImage(ThumbsUpImage,
'rbxasset://textures/ui/Shell/Icons/ThumbsUpIcon', {['720'] = UDim2.new(0,19,0,19); ['1080'] = UDim2.new(0,28,0,28);})
AssetManager.LocalImage(ThumbsDownImage,
'rbxasset://textures/ui/Shell/Icons/ThumbsDownIcon', {['720'] = UDim2.new(0,19,0,19); ['1080'] = UDim2.new(0,28,0,28);})
local VoteWidget = VoteFrameModule(GameGenreContainer,
UDim2.new(0, 60, 0, GameTitleLabel.Position.Y.Offset + 46))
local VoteContainer = VoteWidget:GetContainer()
ThumbsUpImage.Position = UDim2.new(0, VoteContainer.Position.X.Offset - ThumbsUpImage.Size.X.Offset - 10, 0,
VoteContainer.Position.Y.Offset + VoteContainer.Size.Y.Offset - ThumbsUpImage.Size.Y.Offset)
ThumbsDownImage.Position = UDim2.new(0, VoteContainer.Position.X.Offset + VoteContainer.Size.X.Offset + 10, 0,
VoteContainer.Position.Y.Offset)
local SeparatorDot = Utility.Create'ImageLabel'
{
Name = "SeparatorDot";
BackgroundTransparency = 1;
Visible = false;
Parent = GameGenreContainer;
}
AssetManager.LocalImage(SeparatorDot,
'rbxasset://textures/ui/Shell/Icons/SeparatorDot', {['720'] = UDim2.new(0,7,0,7); ['1080'] = UDim2.new(0,10,0,10);})
SeparatorDot.Position = UDim2.new(0, ThumbsDownImage.Position.X.Offset + ThumbsDownImage.Size.X.Offset + 32, 0,
VoteContainer.Position.Y.Offset + (VoteContainer.Size.Y.Offset/2) - (SeparatorDot.Size.Y.Offset/2))
local CreatorIcon = Utility.Create'ImageLabel'
{
Name = "CreatorIcon";
Size = UDim2.new(0, 24, 0, 24);
Position = UDim2.new(0, SeparatorDot.Position.X.Offset + SeparatorDot.Size.X.Offset + 32, 0,
SeparatorDot.Position.Y.Offset + SeparatorDot.Size.Y.Offset/2 - 2 - 10);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/RobloxIcon24.png';
Visible = false;
Parent = GameGenreContainer;
}
local CreatorNameLabel = Utility. Create'TextLabel'
{
Name = "CreatorNameLabel";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, CreatorIcon.Position.X.Offset + CreatorIcon.Size.X.Offset + 8, 0,
SeparatorDot.Position.Y.Offset + SeparatorDot.Size.Y.Offset/2 - 2);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.DescriptionSize;
TextColor3 = GlobalSettings.LightGreyTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Text = "";
Parent = GameGenreContainer;
}
local DescriptionTextLabel = Utility.Create'TextLabel'
{
Name = "DescriptionTextLabel";
Size = UDim2.new(0, 850, 0, 56);
Position = UDim2.new(0, GameTitleLabel.Position.X.Offset, 0, VoteContainer.Position.Y.Offset + VoteContainer.Size.Y.Offset + 20);
BackgroundTransparency = 1;
Text = "";
TextColor3 = GlobalSettings.LightGreyTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
TextWrapped = true;
FontSize = GlobalSettings.DescriptionSize;
Parent = GameGenreContainer;
}
local PlayButton = Utility.Create'ImageButton'
{
Name = "PlayButton";
Position = UDim2.new(0, 0, 1, -77);
Size = UDim2.new(0, 228, 0, 72);
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.GreenButtonColor;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
Parent = GameGenreContainer;
SoundManager:CreateSound('MoveSelection');
ZIndex = 2;
AssetManager.CreateShadow(1);
}
local PlayText = Utility.Create'TextLabel'
{
Name = "PlayText";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Text = string.upper(Strings:LocalizedString("PlayWord"));
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = baseButtonTextColor;
ZIndex = 2;
Parent = PlayButton;
}
local FavoriteButton = Utility.Create'ImageButton'
{
Name = "FavoriteButton";
Position = UDim2.new(0, PlayButton.Size.X.Offset + 10, 1, -77);
Size = UDim2.new(0, 228, 0, 72);
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.GreyButtonColor;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
ZIndex = 2;
Parent = GameGenreContainer;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1);
}
FavoriteButton.NextSelectionRight = FavoriteButton
local FavoriteText = Utility.Create'TextLabel'
{
Name = "FavoriteText";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Text = string.upper(Strings:LocalizedString("FavoriteWord"));
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = baseButtonTextColor;
ZIndex = 2;
Parent = FavoriteButton;
}
local FavoriteStarImage = Utility.Create'ImageLabel'
{
Name = "FavoriteStarImage";
BackgroundTransparency = 1;
Visible = false;
ZIndex = 2;
Parent = FavoriteButton;
}
AssetManager.LocalImage(FavoriteStarImage,
'rbxasset://textures/ui/Shell/Icons/FavoriteStar', {['720'] = UDim2.new(0,21,0,21); ['1080'] = UDim2.new(0,32,0,31);})
FavoriteStarImage.Position = UDim2.new(0, 16, 0.5, -FavoriteStarImage.Size.Y.Offset / 2)
-- Selection Overrides
-- NOTE: This is a fix to prevent unintended selection in the carousel do to the nature of
-- how selection works for a ScrollingFrame.
PlayButton.NextSelectionLeft = PlayButton
FavoriteButton.NextSelectionRight = FavoriteButton
SideBarButton.NextSelectionLeft = SideBarButton
SideBarButton.NextSelectionRight = SideBarButton
--[[ Page Events ]]--
local function toggleFavoriteButton(isFavorited)
if isFavorited == true then
FavoriteStarImage.Visible = true
FavoriteText.Position = UDim2.new(0, FavoriteStarImage.Position.X.Offset + FavoriteStarImage.Size.X.Offset + 12, 0, 0)
FavoriteText.Text = string.upper(Strings:LocalizedString("FavoritedWord"))
FavoriteText.TextXAlignment = Enum.TextXAlignment.Left
elseif isFavorited == false then
FavoriteStarImage.Visible = false
FavoriteText.Position = UDim2.new(0, 0, 0, 0)
FavoriteText.Text = string.upper(Strings:LocalizedString("FavoriteWord"))
FavoriteText.TextXAlignment = Enum.TextXAlignment.Center
end
end
FavoriteButton.SelectionGained:connect(function()
FavoriteButton.ImageColor3 = GlobalSettings.GreySelectedButtonColor
FavoriteText.TextColor3 = selectedButtonTextColor
end)
FavoriteButton.SelectionLost:connect(function()
FavoriteButton.ImageColor3 = GlobalSettings.GreyButtonColor
FavoriteText.TextColor3 = baseButtonTextColor
end)
PlayButton.SelectionGained:connect(function()
PlayButton.ImageColor3 = GlobalSettings.GreenSelectedButtonColor
PlayText.TextColor3 = selectedButtonTextColor
end)
PlayButton.SelectionLost:connect(function()
PlayButton.ImageColor3 = GlobalSettings.GreenButtonColor
PlayText.TextColor3 = baseButtonTextColor
end)
--[[ Content Initialization ]]--
local function setItemsVisible(value)
ThumbsUpImage.Visible = value
ThumbsDownImage.Visible = value
VoteWidget:SetVisible(value)
GameTitleLabel.Visible = value
DescriptionTextLabel.Visible = value
PlayButton.Visible = value
FavoriteButton.Visible = value
SideBarButton.Visible = value
--
SeparatorDot.Visible = value
CreatorIcon.Visible = value
CreatorNameLabel.Visible = value
end
local currentSortCarousel = nil
local currentItemData = nil
local onNewGameSelectedCn = nil
local onNewGameSelectedLateCn = nil
local function setCurrentSortCarousel()
setItemsVisible(false)
toggleFavoriteButton(false)
currentSortCarousel = SortCarousel(UDim2.new(1, 0, 0, 450),
UDim2.new(0, 0, 0, SideBarButton.Position.Y.Offset + SideBarButton.Size.Y.Offset + 56), currentShownCollection, GameGenreContainer)
onNewGameSelectedCn = currentSortCarousel.OnNewGameSelected:connect(function(itemData)
if itemData then
currentItemData = itemData
GameTitleLabel.Text = itemData.Name
DescriptionTextLabel.Text = itemData.Description or ""
CreatorNameLabel.Text = itemData.CreatorName
toggleFavoriteButton(itemData.IsFavorited)
local voteData = itemData.VoteData
if voteData then
local upvotes = voteData.UpVotes
local downvotes = voteData.DownVotes
if upvotes == 0 and downvotes == 0 then
VoteWidget:SetPercentFilled(nil)
else
VoteWidget:SetPercentFilled(upvotes / (upvotes + downvotes))
end
end
setItemsVisible(true)
end
end)
onNewGameSelectedLateCn = currentSortCarousel.OnNewGameSelectedLate:connect(function(description, isFavorited)
toggleFavoriteButton(isFavorited)
DescriptionTextLabel.Text = description or ""
end)
spawn(function()
currentSortCarousel:LoadSortAsync()
if inFocus then
local focusItem = currentSortCarousel:GetItemAt(1)
if focusItem and focusItem:IsDescendantOf(GameGenreContainer) then
GuiService.SelectedCoreObject = focusItem
end
end
end)
end
local function onNewGameSelected(data)
if not data then return end
-- TODO: Update this function when caching is finished
GameTitleLabel.Text = data.Title
CreatorNameLabel.Text = data.CreatorName
local voteData = data.VoteData
if voteData then
local upVotes = voteData.UpVotes
local downVotes = voteData.DownVotes
if upVotes == 0 and downVotes == 0 then
VoteWidget:SetPercentFilled(nil)
else
VoteWidget:SetPercentFilled(upVotes / (upVotes + downVotes))
end
end
DescriptionTextLabel.Text = data.Description or ""
toggleFavoriteButton(data.IsFavorited)
if not data.Description or data.IsFavorited == nil then
spawn(function()
local gameData = GameDataModule:GetGameDataAsync(data.PlaceId)
if gameData then
data.GameData = gameData
data.Description = gameData:GetDescription()
data.IsFavorited = gameData:GetIsFavoritedByUser()
DescriptionTextLabel.Text = data.Description
toggleFavoriteButton(data.IsFavorited)
end
end)
end
setItemsVisible(true)
end
local myCarouselView = nil
local myCarouselController = nil
if isUseNewCarouselInXboxAppEnabled then
myCarouselView = CarouselView()
myCarouselView:SetSize(UDim2.new(0, 1724, 0, 450))
myCarouselView:SetPosition(UDim2.new(0, 0, 0, SideBarButton.Position.Y.Offset + SideBarButton.Size.Y.Offset + 56))
myCarouselView:SetPadding(18)
myCarouselView:SetItemSizePercentOfContainer(2/3)
myCarouselView:SetParent(GameGenreContainer)
myCarouselController = CarouselController(myCarouselView)
end
local function initializeCarouselView()
setItemsVisible(false)
toggleFavoriteButton(false)
myCarouselView:SetParent(nil)
spawn(function()
local loader = LoadingWidget(
{ Parent = GameGenreContainer }, {
function()
if myCarouselController then
myCarouselController:InitializeAsync(currentShownCollection)
end
end
})
loader:AwaitFinished()
loader:Cleanup()
loader = nil
myCarouselView:SetParent(GameGenreContainer)
if this:IsFocused() and myCarouselView then
GuiService.SelectedCoreObject = myCarouselView:GetFocusItem()
end
end)
end
local function canShowFavoritesAsync(collection)
local favoritesPage = collection:GetSortAsync(0, 1)
return favoritesPage and favoritesPage.Count > 0
end
local function canShowRecentAsync(collection)
local recentPage = collection:GetSortAsync(0, 1)
return recentPage and recentPage.Count > 0
end
local function canShowMyPlacesAsync(collection)
local userPlacesPage = collection:GetSortAsync(0, 1)
return userPlacesPage and userPlacesPage.Count > 0
end
local function getSideBarList()
local sideBarList = {}
local favoriteCollection = GameCollection:GetUserFavorites()
if canShowFavoritesAsync(favoriteCollection) then
table.insert(sideBarList,
{ Name = string.upper(Strings:LocalizedString("FavoritesSortTitle")), Collection = favoriteCollection })
end
local recentCollection = GameCollection:GetUserRecent()
if canShowRecentAsync(recentCollection) then
table.insert(sideBarList,
{ Name = string.upper(Strings:LocalizedString("RecentlyPlayedSortTitle")), Collection = recentCollection })
end
table.insert(sideBarList, { Name = string.upper(Strings:LocalizedString("FeaturedTitle")), Collection = GameCollection:GetSort(GameCollection.DefaultSortId.Featured) })
table.insert(sideBarList, { Name = string.upper(Strings:LocalizedString("PopularTitle")), Collection = GameCollection:GetSort(GameCollection.DefaultSortId.Popular) })
table.insert(sideBarList, { Name = string.upper(Strings:LocalizedString("TopRatedTitle")), Collection = GameCollection:GetSort(GameCollection.DefaultSortId.TopRated) })
table.insert(sideBarList, { Name = string.upper(Strings:LocalizedString("TopEarningTitle")), Collection = GameCollection:GetSort(GameCollection.DefaultSortId.TopEarning) })
local userPlacesCollection = GameCollection:GetUserPlaces()
if canShowMyPlacesAsync(userPlacesCollection) then
table.insert(sideBarList,
{ Name = string.upper(Strings:LocalizedString("PlayMyPlaceMoreGamesTitle")), Collection = userPlacesCollection })
end
return sideBarList
end
local function createSideBarAsync()
local sideBar = SideBarModule()
local sideBarList = getSideBarList()
local collectionToIndex = {}
for i = 1, #sideBarList do
local sort = sideBarList[i]
collectionToIndex[sort.Collection] = i
sideBar:AddItem(sort.Name, function()
if sort.Collection ~= currentShownCollection then
currentShownCollection = sort.Collection
TitleLabel.Text = sort.Name
if isUseNewCarouselInXboxAppEnabled then
initializeCarouselView()
else
currentSortCarousel:Destroy()
setCurrentSortCarousel()
end
if this.TransitionTweens then
ScreenManager:DefaultCancelFade(this.TransitionTweens)
this.TransitionTweens = ScreenManager:DefaultFadeIn(GameGenreContainer)
ScreenManager:PlayDefaultOpenSound()
end
end
end)
end
sideBarSelectedCn = Utility.DisconnectEvent(sideBarSelectedCn)
sideBarSelectedCn = SideBarButton.MouseButton1Click:connect(function()
sideBar:SetSelectedObject(collectionToIndex[currentShownCollection])
ScreenManager:OpenScreen(sideBar, false)
end)
end
--[[ Input Events ]]--
PlayButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
local gameData = nil
if isUseNewCarouselInXboxAppEnabled then
if myCarouselController then
gameData = myCarouselController:GetCurrentFocusGameData()
end
else
gameData = currentSortCarousel:GetCurrentSelectedGameData()
end
if gameData then
local placeId = gameData.PlaceId
local creatorUserId = gameData.CreatorUserId
if canJoinGame and returnedFromGame then
canJoinGame = false
GameJoinModule:StartGame(GameJoinModule.JoinType.Normal, placeId, creatorUserId)
canJoinGame = true
end
end
end)
FavoriteButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
local gameData = nil
if isUseNewCarouselInXboxAppEnabled then
if myCarouselController then
gameData = myCarouselController:GetCurrentFocusGameData()
end
else
gameData = currentItemData
end
if gameData and gameData.GameData then
local success, reason = gameData.GameData:PostFavoriteAsync()
if success then
gameData.IsFavorited = gameData.GameData:GetIsFavoritedByUser()
toggleFavoriteButton(gameData.IsFavorited)
elseif reason then
local err = Errors.Favorite[reason]
ScreenManager:OpenScreen(ErrorOverlayModule(err), false)
end
end
end)
--[[ Public API ]]--
function this:SetPosition(newPosition)
GameGenreContainer.Position = newPosition
end
function this:SetParent(newParent)
GameGenreContainer.Parent = newParent
end
function this:GetName()
return TitleLabel.Text
end
function this:Show()
GameGenreContainer.Visible = true
local prevScreen = ScreenManager:GetScreenBelow(self)
if prevScreen and prevScreen.GetName then
BackText.Text = prevScreen:GetName()
else
BackText.Text = ''
end
ScreenManager:DefaultCancelFade(this.TransitionTweens)
self.TransitionTweens = ScreenManager:DefaultFadeIn(GameGenreContainer)
ScreenManager:PlayDefaultOpenSound()
end
function this:Hide()
GameGenreContainer.Visible = false
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
end
function this:Destroy()
if onNewGameSelectedCn then
onNewGameSelectedCn:disconnect()
onNewGameSelectedCn = nil
end
if not isUseNewCarouselInXboxAppEnabled then
currentSortCarousel:Destroy()
end
VoteWidget:Destroy()
GameGenreContainer:Destroy()
end
function this:IsFocused()
return inFocus
end
function this:Focus()
inFocus = true
if isUseNewCarouselInXboxAppEnabled then
local selection = myCarouselView:GetFocusItem() or SideBarButton
GuiService.SelectedCoreObject = selection
else
if self.SavedSelectedObject and self.SavedSelectedObject:IsDescendantOf(GameGenreContainer) then
GuiService.SelectedCoreObject = self.SavedSelectedObject
end
end
ContextActionService:BindCoreAction("ReturnFromGameGenreScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
self:Destroy()
end
end,
false,
Enum.KeyCode.ButtonB)
if PlatformService then
dataModelViewChangedCn = PlatformService.ViewChanged:connect(function(viewType)
if viewType == 0 then
returnedFromGame = false
wait(1)
returnedFromGame = true
end
end)
end
if isUseNewCarouselInXboxAppEnabled then
if myCarouselController then
newGameSelectedCn = myCarouselController.NewItemSelected:connect(onNewGameSelected)
onNewGameSelected(myCarouselController:GetCurrentFocusGameData())
myCarouselController:Connect()
end
end
spawn(function()
createSideBarAsync()
end)
end
function this:RemoveFocus()
inFocus = false
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(GameGenreContainer) then
self.SavedSelectedObject = selectedObject
GuiService.SelectedCoreObject = nil
end
ContextActionService:UnbindCoreAction("ReturnFromGameGenreScreen")
dataModelViewChangedCn = Utility.DisconnectEvent(dataModelViewChangedCn)
newGameSelectedCn = Utility.DisconnectEvent(newGameSelectedCn)
sideBarSelectedCn = Utility.DisconnectEvent(sideBarSelectedCn)
if isUseNewCarouselInXboxAppEnabled then
if myCarouselController then
myCarouselController:Disconnect()
end
end
end
if isUseNewCarouselInXboxAppEnabled then
initializeCarouselView()
else
setCurrentSortCarousel()
end
return this
end
return CreateGameGenre
@@ -0,0 +1,48 @@
--[[
// GameJoin.lua
// Handles game join logic
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local UserData = require(Modules:FindFirstChild('UserData'))
local GameJoin = {}
GameJoin.JoinType = {
Normal = 0; -- use placeId
GameInstance = 1; -- use game instance id
Follow = 2; -- use userId or user you are following
PMPCreator = 3; -- use placeId, used when a player joins their own place
}
-- joinType - GameJoin.JoinType
-- joinId - can be a userId or placeId, see JoinType for which one to use
function GameJoin:StartGame(joinType, joinId, creatorUserId)
if UserSettings().GameSettings:InStudioMode() then
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.Test.CannotJoinGame), false)
else
local success, result = pcall(function()
-- check if we are the creator for normal joins
if joinType == self.JoinType.Normal and creatorUserId == UserData:GetRbxUserId() then
joinType = self.JoinType.PMPCreator
end
return PlatformService:BeginStartGame3(joinType, joinId)
end)
-- catch pcall error, something went wrong with call into API
-- all other game join errors are caught in AppHome.lua
if not success then
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.GameJoin[#Errors.GameJoin]), false)
end
end
end
return GameJoin
@@ -0,0 +1,233 @@
-- Copyright ROBLOX 2015
--[[
Filename: GamePane.lua
Written By: Kyler Mulherin, Jason Roth
TODO:
Make responsive
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local GuiService = game:GetService('GuiService')
local GameSort = require(Modules:FindFirstChild('GameSort'))
local SortData = require(Modules:FindFirstChild('SortData'))
local ScrollingGrid = require(Modules:FindFirstChild('ScrollingGrid'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local GameCollection = require(Modules:FindFirstChild('GameCollection'))
--CONSTANTS
local SPACING = 33
local function CreateGamePane(parent)
local this = {}
local sortCatagories = nil
local imageSize = UDim2.new(0, 298, 0, 298)
local spacing = Vector2.new(14, 14)
local rows, columns = 2, 2
local views = {}
local inFocus = false
local noSelectionObject = Utility.Create'ImageLabel'
{
BackgroundTransparency = 1;
}
-- UI Elements
local GamePaneContainer = Utility.Create'Frame'
{
Name = 'GamePane';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Parent = parent;
SelectionImageObject = noSelectionObject;
Visible = false;
}
local gameSortsPane = ScrollingGrid()
gameSortsPane:SetPosition(UDim2.new(0, 0, 0, 0))
gameSortsPane:SetCellSize(Vector2.new(610, 646))
gameSortsPane:SetSize(UDim2.new(1, GlobalSettings.TitleSafeInset.X.Offset, 1, 0))
gameSortsPane:SetScrollDirection(gameSortsPane.Enum.ScrollDirection.Horizontal)
gameSortsPane:SetParent(GamePaneContainer)
gameSortsPane:SetClipping(false)
gameSortsPane:SetSpacing(Vector2.new(SPACING, SPACING))
gameSortsPane.Container.Visible = false
local function setSelectionOnLoad()
if this:IsFocused() then
GuiService.SelectedCoreObject = this:GetDefaultSelection()
end
end
local setSortsDebounce = false
local function setSortView()
if setSortsDebounce then return end
setSortsDebounce = true
if not sortCatagories then
spawn(function()
sortCatagories = SortData.GetSortCategoriesAsync()
end)
end
local function createView(gameCollection, name)
local sortPage = gameCollection:GetSortAsync(0, 5)
if sortPage and sortPage.Count > 0 then
local view = GameSort:CreateGridView(UDim2.new(), imageSize, spacing, rows, columns)
local iconIds = sortPage:GetPageIconIds()
local names = sortPage:GetPagePlaceNames()
local placeIds = sortPage:GetPagePlaceIds()
view:SetImages(iconIds)
view:ConnectInput(placeIds, names, iconIds, name, gameCollection)
view:SetTitle(name)
return view
end
return nil
end
local newViews = {}
local function loadSortsAsync()
while not sortCatagories do
wait()
end
-- create all the views
for i = 1, #sortCatagories do
local sort = sortCatagories[i]
local sortCollection = GameCollection:GetSort(sort["Id"])
table.insert(newViews, createView(sortCollection, sort["Name"]))
end
local favoriteGamesCollection = GameCollection:GetUserFavorites()
table.insert(newViews, createView(favoriteGamesCollection, Strings:LocalizedString("FavoritesSortTitle")))
local recentGamesCollection = GameCollection:GetUserRecent()
table.insert(newViews, createView(recentGamesCollection, Strings:LocalizedString("RecentlyPlayedSortTitle")))
local userPlacesCollection = GameCollection:GetUserPlaces()
table.insert(newViews, createView(userPlacesCollection, Strings:LocalizedString("PlayMyPlaceMoreGamesTitle")))
end
local loader = LoadingWidget(
{ Parent = GamePaneContainer }, { loadSortsAsync })
spawn(function()
loader:AwaitFinished()
loader:Cleanup()
loader = nil
-- Remove old items
gameSortsPane:RemoveAllItems()
views = newViews
for i = 1, #views do
gameSortsPane:AddItem(views[i]:GetContainer())
end
gameSortsPane.Container.Visible = true
setSelectionOnLoad()
if GamePaneContainer.Visible then
this.TransitionTweens = ScreenManager:DefaultFadeIn(gameSortsPane.Container)
ScreenManager:PlayDefaultOpenSound()
end
setSortsDebounce = false
end)
end
-- SCREEN FUNCTIONS
function this:SetPosition(newPosition)
GamePaneContainer.Position = newPosition
end
function this:SetParent(newParent)
GamePaneContainer.Parent = newParent
end
function this:IsAncestorOf(object)
return GamePaneContainer and GamePaneContainer:IsAncestorOf(object)
end
function this:GetName()
return Strings:LocalizedString('GameWord')
end
function this:GetDefaultSelection()
for i = 1, #views do
if views[i]:GetDefaultSelection() then
return views[i]:GetDefaultSelection()
end
end
return GamePaneContainer
end
function this:ViewsContainObject(guiObject)
if guiObject then
for i = 1, #views do
if views[i]:Contains(guiObject) then
return true
end
end
end
return false
end
function this:IsFocused()
return inFocus
end
function this:Show()
ScreenManager:DefaultCancelFade(self.TransitionTweens)
setSortView()
GamePaneContainer.Visible = true
end
function this:Hide()
GamePaneContainer.Visible = false
gameSortsPane.Container.Visible = false
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
end
function this:Focus()
inFocus = true
if self.SavedSelectObject and self:ViewsContainObject(self.SavedSelectObject) then
GuiService.SelectedCoreObject = self.SavedSelectObject
else
GuiService.SelectedCoreObject = self:GetDefaultSelection()
end
-- Clear out the saved selected object, it has done its work
self.SavedSelectObject = nil
end
function this:RemoveFocus()
inFocus = false
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and self:ViewsContainObject(selectedObject) then
self.SavedSelectObject = selectedObject
else
self.SavedSelectObject = nil
end
if selectedObject and (selectedObject == GamePaneContainer or self:IsAncestorOf(selectedObject)) then
GuiService.SelectedCoreObject = nil
end
end
--[[ Initialize - Don't Block ]]--
spawn(function()
sortCatagories = SortData.GetSortCategoriesAsync()
end)
return this
end
return CreateGamePane
@@ -0,0 +1,283 @@
--[[
// GameSort.lua
// Creates a grid layout for a game sort
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local PopupText = require(Modules:FindFirstChild('PopupText'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local GameCollection = require(Modules:FindFirstChild('GameCollection'))
local GameSort = {}
local function createSortContainer(size)
local sortContainer = Utility.Create'Frame'
{
Name = "SortContainer";
Size = size;
BackgroundTransparency = 1;
}
return sortContainer
end
local function createSortTitle(name)
local sortTitleLabel = Utility.Create'TextLabel'
{
Name = "SortTitle";
Size = UDim2.new(1, 0, 0, 36);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = string.upper(name);
Parent = sortContainer;
}
return sortTitleLabel
end
local function createMoreButton(margin, parent)
-- we override the selection on moreButton to fit around the moreImage
local overrideSelection = Utility.Create'ImageLabel'
{
Name = "OverrideSelection";
Image = 'rbxasset://textures/ui/SelectionBox.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(19,19,43,43);
BackgroundTransparency = 1;
}
local moreButton = Utility.Create'ImageButton'
{
Name = "MoreButton";
Size = UDim2.new(1, 0, 0, 50);
Position = UDim2.new(0, 0, 1, margin);
BackgroundTransparency = 1;
ZIndex = 2;
Visible = false;
SelectionImageObject = overrideSelection;
Parent = parent;
SoundManager:CreateSound('MoveSelection');
}
local moreImage = Utility.Create'ImageLabel'
{
Name = "MoreImage";
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 2;
Parent = moreButton;
}
local function updateMoreImage(isSelected)
local uri = isSelected and 'rbxasset://textures/ui/Shell/Buttons/MoreButtonSelected' or 'rbxasset://textures/ui/Shell/Buttons/MoreButton'
AssetManager.LocalImage(moreImage, uri, {['720'] = UDim2.new(0,72,0,33); ['1080'] = UDim2.new(0,108,0,50);})
moreImage.Position = UDim2.new(1, -moreImage.Size.X.Offset, 0, 0)
overrideSelection.Size = UDim2.new(0, moreImage.Size.X.Offset + 14, 0, moreImage.Size.Y.Offset + 14)
overrideSelection.Position = UDim2.new(1, -overrideSelection.Size.X.Offset + 7, 0, -7)
end
moreButton.SelectionGained:connect(function()
updateMoreImage(true)
end)
moreButton.SelectionLost:connect(function()
updateMoreImage(false)
end)
updateMoreImage(GuiService.SelectedCoreObject == moreButton)
return moreButton
end
local function createImageButton(size, position)
return Utility.Create'ImageButton'
{
Name = "GameThumbButton";
Size = size;
Position = position;
BackgroundTransparency = 0;
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
ZIndex = 2;
AssetManager.CreateShadow(1);
SoundManager:CreateSound('MoveSelection');
}
end
local function createImageGrid(rows, columns, size, spacing, offset, images)
for i = 1, rows do
for j = 1, columns do
local image = createImageButton(size,
UDim2.new(0, (j - 1) * size.X.Offset + (j - 1) * spacing.x, 0, offset + (i - 1) * size.Y.Offset + (i - 1) * spacing.y))
table.insert(images, image)
image.Name = tostring(#images)
end
end
end
local function createPopupText(images)
local popupText = {}
for i = 1, #images do
local popup = PopupText(images[i], "")
table.insert(popupText, popup)
end
return popupText
end
local function setImages(imageIds, images)
local size = ThumbnailLoader.Sizes.Medium
local assetType = ThumbnailLoader.AssetType.Icon
for i = 1, #images do
if imageIds[i] then
local thumbLoader = ThumbnailLoader:Create(images[i], imageIds[i], size, assetType)
spawn(function()
if not thumbLoader:LoadAsync(true, false) then
-- TODO
end
end)
else
images[i].BackgroundColor3 = GlobalSettings.GreyButtonColor
images[i].Image = ""
local image = Utility.Create'ImageLabel'
{
Name = "NoGameImage";
Size = UDim2.new(0, 102, 0, 102);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/GamePlusIcon.png';
ZIndex = images[i].ZIndex;
Parent = images[i];
}
Utility.CalculateAnchor(image, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
end
end
end
local function createBaseGrid(size, spacing, images)
local this = {}
this.Container = createSortContainer(size)
this.Title = createSortTitle("")
this.Title.Parent = this.Container
this.MoreButton = createMoreButton(spacing, this.Container)
for i = 1, #images do
images[i].Parent = this.Container
end
local popupText = createPopupText(images)
function this:SetParent(newParent)
self.Container.Parent = newParent
end
function this:GetContainer()
return self.Container
end
function this:SetPosition(newPosition)
self.Container.Position = newPosition
end
function this:SetTitle(newTitle)
self.Title.Text = string.upper(newTitle)
end
function this:SetVisible(value)
self.Container.Visible = value
end
function this:SetImages(imageIds)
setImages(imageIds, images)
end
function this:ConnectInput(ids, names, iconIds, sortName, gameCollection)
for i = 1, #images do
if ids[i] then
images[i].MouseButton1Click:connect(function()
EventHub:dispatchEvent(EventHub.Notifications["OpenGameDetail"], ids[i], names[i], iconIds[i])
end)
else
images[i].MouseButton1Click:connect(function()
-- If there are not enough games in my games section then let them know how to make more!
if gameCollection == GameCollection:GetUserPlaces() then
local titleAndMsg = {
Title = Strings:LocalizedString('PlayMyPlaceMoreGamesTitle');
Msg = Strings:LocalizedString('PlayMyPlaceMoreGamesPhrase');
}
ScreenManager:OpenScreen(ErrorOverlay(titleAndMsg, true), false)
else
-- if there is no game to load in this slot, we're going to redirect to the featured sort if the user presses this button
EventHub:dispatchEvent(EventHub.Notifications["OpenGameGenre"], Strings:LocalizedString('FeaturedTitle'), GameCollection:GetSort(3))
end
end)
end
if popupText[i] then
popupText[i]:SetText(names[i] or Strings:LocalizedString("MoreGamesPhrase"))
end
end
self.MoreButton.Visible = #ids > #images
self.MoreButton.MouseButton1Click:connect(function()
EventHub:dispatchEvent(EventHub.Notifications["OpenGameGenre"], sortName, gameCollection)
end)
end
function this:GetDefaultSelection()
local default = nil
if #images > 0 then
default = images[1]
end
return default
end
function this:Contains(guiObject)
for i = 1, #images do
if images[i] == guiObject then
return true
end
end
return false
end
function this:Destroy()
self.Container:Destroy()
end
return this
end
function GameSort:CreateMainGridView(size, spacing, lgImageSize, smImageSize)
local images = {}
local mainImage = createImageButton(lgImageSize, UDim2.new(0, 0, 0, 36))
table.insert(images, mainImage)
mainImage.Name = tostring(#images)
for i = 1, 2 do
local smImage = createImageButton(smImageSize,
UDim2.new(0, (i - 1) * smImageSize.X.Offset + (i - 1) * spacing.x, 1, -smImageSize.Y.Offset))
table.insert(images, smImage)
smImage.Name = tostring(#images)
end
local this = createBaseGrid(size, 12, images)
return this
end
-- 2x2 grid for the games page
function GameSort:CreateGridView(size, imageSize, spacing, rows, columns)
local images = {}
createImageGrid(rows, columns, imageSize, spacing, 36, images)
local this = createBaseGrid(size, 12, images)
return this
end
return GameSort
@@ -0,0 +1,96 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local Settings =
{
-- As per microsoft reccomendations
ActionSafeInset = UDim2.new((128 / 1920) * 0.5, 0, (64 / 1080) * 0.5, 0);
TitleSafeInset = UDim2.new((72 / 1792) * 0.5, 0, (16 / 1080) * 0.5, 0);
AbsoluteTitleSafeInset = UDim2.new((200 / 1920) * 0.5, 0, (80 / 1080) * 0.5, 0);
WhiteTextColor = Color3.new(1,1,1);
GreyTextColor = Color3.new(0.5,0.5,0.5);
LightGreyTextColor = Color3.new(184/255, 184/255, 184/255);
BlueTextColor = Color3.new(0,116/255,189/255);
BlackTextColor = Color3.new(0,0,0);
GreenTextColor = Color3.new(2/255, 183/255, 87/255);
RedTextColor = Color3.new(216/255, 104/255, 104/255);
TextSelectedColor = Color3.new(19/255, 19/255, 19/255);
LineBreakColor = Color3.new(78/255, 78/255, 78/255);
PageDivideColor = Color3.new(151/255, 151/255, 151/255);
BadgeOwnedColor = Color3.new(45/255, 96/255, 128/255);
BadgeOverlayColor = Color3.new(13/255, 28/255, 38/255);
OverlayColor = Color3.new(26/255, 57/255, 76/255);
BadgeFrameColor = Color3.new(106/255, 120/255, 129/255);
RobuxOverlayImageColor = Color3.new(42/255, 51/255, 57/255);
BlueButtonColor = Color3.new(50/255, 181/255, 1);
GreySelectionColor = Color3.new(84/255, 99/255, 109/255);
GreenButtonColor = Color3.new(2/255, 163/255, 77/255);
GreenSelectedButtonColor = Color3.new(63/255, 198/255, 121/255);
GreyButtonColor = Color3.new(78/255, 84/255, 96/255);
GreySelectedButtonColor = Color3.new(50/255, 181/255, 1);
CharacterBackgroundColor = Color3.new(39/255, 69/255, 82/255);
ForegroundGreyColor = Color3.new(58/255, 60/255, 64/255);
BackgroundGreyColor = Color3.new(78/255, 84/255, 96/255);
ModalBackgroundColor = Color3.new(0,0,0);
AvatarBoxBackgroundColor = Color3.new(255/255,255/255,255/255);
TabUnderlineColor = Color3.new(50/255,181/255,255/255);
PriceLabelColor = Color3.new(241/255, 116/255, 10/255);
TextBoxColor = Color3.new(1, 1, 1);
TextBoxSelectedTransparency = 0.5;
TextBoxDefaultTransparency = 0.75;
AvatarBoxBackgroundSelectedTransparency = 0.75;
AvatarBoxBackgroundDeselectedTransparency = 0.875;
AvatarBoxTextSelectedTransparency = 0;
AvatarBoxTextDeselectedTransparency = 0.5;
ModalBackgroundTransparency = 0.3;
FriendStatusTextTransparency = 0.5;
LargeHeadingSize = Enum.FontSize.Size48;
MediumLargeHeadingSize = Enum.FontSize.Size36;
MediumHeadingSize = Enum.FontSize.Size24;
SmallHeadingSize = Enum.FontSize.Size18;
ParagraphSize = Enum.FontSize.Size14;
-- Font Sizes
-- RobloxSize -> Mockup Sizes
LargeFontSize = Enum.FontSize.Size96; -- 72pt
HeaderSize = Enum.FontSize.Size60; -- 48pt
MediumFontSize = Enum.FontSize.Size48; -- 36pt
TitleSize = Enum.FontSize.Size42; -- 34pt
ButtonSize = Enum.FontSize.Size36; -- 30pt
DescriptionSize = Enum.FontSize.Size32; -- 26pt
SubHeaderSize = Enum.FontSize.Size28; -- 24pt
SmallTitleSize = Enum.FontSize.Size24; -- 20pt
HeadingFont = Enum.Font.SourceSans;
-- Font Types
RegularFont = Enum.Font.SourceSans;
LightFont = Enum.Font.SourceSansLight;
BoldFont = Enum.Font.SourceSansBold;
ItalicFont = Enum.Font.SourceSansItalic;
TabItemSpacing = 30;
-- Values for tinting the background scenery
SceneBrightness = 0.3;
SceneContrast = 0.5;
SceneGrayscaleLevel = 1;
SceneTintColor = Color3.new(20.0 / 255.0, 43.0 / 255.0, 60.0 / 255.0);
SceneBlurIntensity = 24;
SceneMotionBlurIntensity = 3;
-- Screen priority
DefaultPriority = 1;
ElevatedPriority = 2;
ImmediatePriority = 3;
}
return Settings
@@ -0,0 +1,124 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
-- Herostats Manager
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local SortDataModule = Modules:FindFirstChild('SortData')
local EventHub = require(Modules:FindFirstChild('EventHub'))
local Http = require(Modules:FindFirstChild('Http'))
local UserData = require(Modules:FindFirstChild('UserData'))
local PlatformInterface = require(Modules:FindFirstChild('PlatformInterface'))
local VIEW_GAMETYPE_ENUM =
{
AppShell = 0;
Game = 1;
}
local HeroStatsManager = {}
function HeroStatsManager:SendHeroStatsEventAsync(heroStatName, setValue)
print("HeroStatsManager - event name:" , heroStatName , "event value:" , setValue)
local heroStatStatus = nil
local success, msg = pcall(function()
-- NOTE: Yielding function
if PlatformService and not UserSettings().GameSettings:InStudioMode() then
heroStatStatus = PlatformService:BeginHeroStat(heroStatName, setValue)
end
end)
if not success then
-- NOTE: very likely this function ever throws an error but returns error codes
print("HeroStatsManager - event name:" , heroStatName , "value" , setValue , "for reason:" , msg)
end
print("HeroStatsManager - event name:" , heroStatName , "event status:" , heroStatStatus)
end
local function UpdateEquippedPackagesAsync()
-- print("Update Equipped Packages")
local myUserId = UserData:GetRbxUserId()
local packages = myUserId and Http.GetUserOwnedPackagesAsync(myUserId)
local data = packages and packages['IsValid'] and packages['Data']
local items = data and data['Items']
-- local Utility = require(Modules:FindFirstChild('Utility'))
-- print("Update Equipped Packages DATA:" , Utility.PrettyPrint(myUserId), Utility.PrettyPrint(packages), Utility.PrettyPrint(data), Utility.PrettyPrint(items))
if items then
local numberOwnedPackages = #items
HeroStatsManager:SendHeroStatsEventAsync("AvatarsEquipped", numberOwnedPackages)
end
end
local joinDebounce = false
local function OnJoinedGameAsync()
if joinDebounce then return end
joinDebounce = true
HeroStatsManager:SendHeroStatsEventAsync("GamesCount")
joinDebounce = false
end
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentPackage"], "HeroStatsManager",
function(packageId)
spawn(UpdateEquippedPackagesAsync)
end)
EventHub:addEventListener(EventHub.Notifications["AuthenticationSuccess"], "HeroStatsManager",
function()
spawn(UpdateEquippedPackagesAsync)
end)
if PlatformService then
PlatformService.ViewChanged:connect(function(newView)
if newView == VIEW_GAMETYPE_ENUM['Game'] then
spawn(OnJoinedGameAsync)
end
end)
end
spawn(function()
if UserSettings().GameSettings:InStudioMode() then return end
local last = nil
while true do
local partyMembers = PlatformInterface:GetPartyMembersAsync()
local inParty = PlatformInterface:IsInAParty(partyMembers)
local current = tick()
if inParty then
if last then
if current - last > 60 then
HeroStatsManager:SendHeroStatsEventAsync("PartyCount")
last = last + 60
end
else
last = current
end
else
last = nil
end
wait(60)
end
end)
return HeroStatsManager
@@ -0,0 +1,483 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = Game:GetService("CoreGui")
local GuiService = game:GetService('GuiService')
local PlayersService = game:GetService("Players")
local RunService = game:GetService("RunService")
local ContextActionService = game:GetService('ContextActionService')
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local UserData = require(Modules:FindFirstChild('UserData'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local FriendsData = require(Modules:FindFirstChild('FriendsData'))
local FriendsView = require(Modules:FindFirstChild('FriendsView'))
local ScrollingGridModule = require(Modules:FindFirstChild('ScrollingGrid'))
local GameSort = require(Modules:FindFirstChild('GameSort'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local GameCollection = require(Modules:FindFirstChild("GameCollection"))
local MOCKUP_SIZE = Vector2.new(1920, 1080)
local PROFILE_SIZE = Vector2.new(450, 343)
local PROFILE_NAME_SIZE = Vector2.new(450, 38)
local PROFILE_BUTTON_SIZE = Vector2.new(450, 300)
local PROFILE_IMAGE_SIZE = Vector2.new(450, 300)
local PROFILE_AVATAR_BRUSH_SIZE = Vector2.new(301, 149)
local SORTS_SIZE = Vector2.new(1234, 608)
local function CreateHomePane(parent)
local this = {}
local inFocus = false
local sortsObjects = {}
local HomePaneContainer = Utility.Create'Frame'
{
Name = 'HomePane';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Parent = parent;
}
local ProfileContainer = Utility.Create'Frame'
{
Name = 'ProfileContainer';
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
Parent = HomePaneContainer;
}
local NameLabel = Utility.Create'TextLabel'
{
Name = 'NameLabel';
Text = '';
TextXAlignment = 'Left';
TextYAlignment = 'Top';
-- TextScaled = true;
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
BackgroundTransparency = 1;
Parent = ProfileContainer;
};
local ProfileButton = Utility.Create'ImageButton'
{
Name = 'ProfileButton';
AutoButtonColor = false;
BackgroundTransparency = 1;
Parent = ProfileContainer;
SoundManager:CreateSound('MoveSelection');
}
local ProfileImage = Utility.Create'ImageLabel'
{
Name = 'ProfileImage';
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 3;
Parent = ProfileButton;
};
local ProfileImageBackground = Utility.Create'Frame'
{
Name = 'ProfileImageBackground';
Size = UDim2.new(1,0,1,0);
BorderSizePixel = 0;
BackgroundTransparency = 0;
BackgroundColor3 = GlobalSettings.CharacterBackgroundColor;
ZIndex = 2;
Parent = ProfileImage;
AssetManager.CreateShadow(1);
}
local AvatarBrushBackground = Utility.Create'Frame'
{
Name = 'AvatarBrushImage';
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.AvatarBoxBackgroundColor;
BackgroundTransparency = GlobalSettings.AvatarBoxBackgroundDeselectedTransparency;
ZIndex = 2;
-- Parent = ProfileButton;
}
local AvatarBrushImage = Utility.Create'ImageLabel'
{
Name = 'AvatarBrushImage';
BorderSizePixel = 0;
BackgroundTransparency = 1;
ZIndex = 2;
Parent = AvatarBrushBackground;
};
AssetManager.LocalImage(AvatarBrushImage,
'rbxasset://textures/ui/Shell/Icons/CustomizeIcon', {['720'] = UDim2.new(0,31,0,32); ['1080'] = UDim2.new(0,47,0,48);})
local AvatarTextLabel = Utility.Create'TextLabel'
{
Name = 'AvatarTextLabel';
Text = Strings:LocalizedString('EditAvatarPhrase');
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
ZIndex = 2;
Parent = AvatarBrushBackground;
};
ProfileButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
EventHub:dispatchEvent(EventHub.Notifications["NavigateToEquippedAvatar"])
-- EventHub:dispatchEvent(EventHub.Notifications["OpenProfileDetail"], "AppHub");
end)
local function OnProfileButtonSelectionChanged()
local selectedObject = GuiService.SelectedCoreObject
local newBackgroundTransparency = selectedObject == ProfileButton and
GlobalSettings.AvatarBoxBackgroundSelectedTransparency or
GlobalSettings.AvatarBoxBackgroundDeselectedTransparency
local newTextTransparency = selectedObject == ProfileButton and
GlobalSettings.AvatarBoxTextSelectedTransparency or
GlobalSettings.AvatarBoxTextDeselectedTransparency
Utility.PropertyTweener(AvatarBrushBackground, 'BackgroundTransparency', newBackgroundTransparency, newBackgroundTransparency, 0, nil, true)
Utility.PropertyTweener(AvatarTextLabel, 'TextTransparency', newTextTransparency, newTextTransparency, 0, nil, true)
Utility.PropertyTweener(AvatarBrushImage, 'ImageTransparency', newTextTransparency, newTextTransparency, 0, nil, true)
end
local existingThumbnailLoader = nil
local function UpdateProfileInfo()
local playerName = UserData:GetDisplayName()
NameLabel.Text = playerName and playerName:upper() or ''
-- ProfileImage.Image = UserData:GetAvatarUrl(420, 420)..'&cb='..tostring(tick())
local rbxuid = UserData:GetRbxUserId()
if rbxuid then
if existingThumbnailLoader then
existingThumbnailLoader:Cancel()
end
local thumbnailSize = ThumbnailLoader.Sizes.Medium
local thumbLoader = ThumbnailLoader:Create(ProfileImage, rbxuid,
thumbnailSize, ThumbnailLoader.AssetType.Avatar, true)
existingThumbnailLoader = thumbLoader
spawn(function()
thumbLoader:LoadAsync()
-- ProfileImage.ImageRectOffset = Vector2.new(thumbnailSize.X, 0)
-- ProfileImage.ImageRectSize = Vector2.new(-thumbnailSize.X, (PROFILE_IMAGE_SIZE.Y/PROFILE_IMAGE_SIZE.X) * thumbnailSize.X)
ProfileImage.ImageRectSize = Vector2.new(thumbnailSize.X, (PROFILE_IMAGE_SIZE.Y/PROFILE_IMAGE_SIZE.X) * thumbnailSize.X)
end)
end
-- local thumbLoader = ThumbnailLoader:Create(ProfileImage, data.AssetId, ThumbnailLoader.Sizes.Medium)
-- spawn(function()
-- thumbLoader:LoadAsync()
-- end)
end
UpdateProfileInfo()
local FriendActivityContainer = Utility.Create'Frame'
{
Name = 'FriendActivityContainer';
BackgroundTransparency = 1;
Parent = HomePaneContainer;
}
local FriendActivityTitle = Utility.Create'TextLabel'
{
Name = 'FriendActivityTitle';
Text = Strings:LocalizedString('FriendActivityWord'):upper();
Size = UDim2.new(1,0,0,50);
Position = UDim2.new(0,0,0,10);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
BackgroundTransparency = 1;
Parent = FriendActivityContainer;
};
local FriendsStatusMessage = Utility.Create'TextLabel'
{
Name = 'FriendsStatusMessage';
Text = Strings:LocalizedString('NoFriendsOnlinePhrase');
Size = UDim2.new(0.9,0,1,-125);
Position = UDim2.new(0.05, 0, 0, 125);
TextYAlignment = 'Top';
TextColor3 = GlobalSettings.GreyTextColor;
TextWrapped = true;
TextTransparency = GlobalSettings.FriendStatusTextTransparency;
Font = GlobalSettings.BoldFont;
FontSize = GlobalSettings.DescriptionSize;
BackgroundTransparency = 1;
Visible = false;
Parent = FriendActivityContainer;
};
local friendsScroller = ScrollingGridModule()
friendsScroller:SetSize(UDim2.new(1,0,1,-60))
friendsScroller:SetRowColumnConstraint(1)
friendsScroller:SetScrollDirection(friendsScroller.Enum.ScrollDirection.Vertical)
friendsScroller:SetCellSize(Vector2.new(446,114))
friendsScroller:SetSpacing(Vector2.new(0, 26))
friendsScroller:SetPosition(UDim2.new(0,0,0,FriendActivityTitle.Position.Y.Offset + FriendActivityTitle.Size.Y.Offset))
local friendScrollerContainer = friendsScroller:GetGuiObject()
friendScrollerContainer.Visible = false
friendsScroller:SetParent(FriendActivityContainer)
local function setFriendItems()
local function onFriendsUpdated(friendCount)
FriendsStatusMessage.Visible = friendCount < 1
end
local friendsData = FriendsData.GetOnlineFriendsAsync()
local myFriendsView = FriendsView(friendsScroller, friendsData, nil, onFriendsUpdated)
onFriendsUpdated(#friendsData)
end
local friendsLoader = LoadingWidget(
{ Parent = FriendActivityContainer }, { setFriendItems } )
-- Don't Block
spawn(function()
friendsLoader:AwaitFinished()
friendsLoader:Cleanup()
friendScrollerContainer.Visible = true
end)
local SortsContainer = Utility.Create'Frame'
{
Name = 'SortsContainer';
BackgroundTransparency = 1;
Parent = HomePaneContainer;
}
local populateSortsDebounce = false
local function PopulateSorts()
if populateSortsDebounce then return end
populateSortsDebounce = true
local function loadGameSorts()
while #sortsObjects > 0 do
local sortObject = table.remove(sortsObjects)
if sortObject then
if this.SavedSelectedObject and this.SavedSelectedObject:IsDescendantOf(sortObject:GetContainer()) then
this.SavedSelectedObject = nil
end
if GuiService.SelectedCoreObject and GuiService.SelectedCoreObject:IsDescendantOf(sortObject:GetContainer()) then
if inFocus then
GuiService.SelectedCoreObject = this:GetDefaultSelectionObject()
end
end
sortObject:Destroy()
end
end
local favoriteGamesCollection = GameCollection:GetUserFavorites()
local favoritesPage1 = favoriteGamesCollection:GetSortAsync(0, 4)
local recentGamesCollection = GameCollection:GetUserRecent()
local recentlyPage1 = recentGamesCollection:GetSortAsync(0, 4)
local showRecent = recentlyPage1 and #recentlyPage1:GetPagePlaceIds() > 2
local showFavorites = favoritesPage1 and #favoritesPage1:GetPagePlaceIds() > 2
local function setGridView(view, page, title, collection)
if page then
local names = page:GetPagePlaceNames()
local placeIds = page:GetPagePlaceIds()
local iconIds = page:GetPageIconIds()
view:SetImages(iconIds)
view:ConnectInput(placeIds, names, iconIds, title, collection)
end
view:SetTitle(title)
view:SetVisible(false)
view:SetParent(SortsContainer)
table.insert(sortsObjects, view)
end
local function createMainSortGrid(title, page, collection)
local view = GameSort:CreateMainGridView(UDim2.new(0, 378, 1, 0), Vector2.new(10, 10),
UDim2.new(1, 0, 0, 378), UDim2.new(0, 184, 0, 184))
setGridView(view, page, title, collection)
return view
end
local function createGridSortView(size, imageSize, spacing, rows, columns, page, title, collection)
local view = GameSort:CreateGridView(size, imageSize, spacing, rows, columns)
setGridView(view, page, title, collection)
return view
end
local currentPosition = UDim2.new(0, 0, 0, 0)
local margin = (showFavorites and showRecent) and 50 or 28
if showFavorites then
local view = createMainSortGrid(Strings:LocalizedString('FavoritesSortTitle'), favoritesPage1, favoriteGamesCollection)
view:SetPosition(currentPosition)
currentPosition = currentPosition + UDim2.new(0, view:GetContainer().Size.X.Offset + margin, 0, 0)
end
if showRecent then
local view = createMainSortGrid(Strings:LocalizedString('RecentlyPlayedSortTitle'), recentlyPage1, recentGamesCollection)
view:SetPosition(currentPosition)
currentPosition = currentPosition + UDim2.new(0, view:GetContainer().Size.X.Offset + margin, 0, 0)
end
local featuredCollection = GameCollection:GetSort(GameCollection.DefaultSortId.Featured)
local featuredTitle = Strings:LocalizedString('FeaturedTitle')
local recommendedView = nil
if showRecent and showFavorites then
local page = featuredCollection:GetSortAsync(0, 4)
recommendedView = createMainSortGrid(featuredTitle, page, featuredCollection)
elseif showRecent or showFavorites then
local page = featuredCollection:GetSortAsync(0, 7)
recommendedView = createGridSortView(UDim2.new(0, 858, 1, 0), UDim2.new(0, 276, 0, 276), Vector2.new(15, 20),
2, 3, page, featuredTitle, featuredCollection)
else
local page = featuredCollection:GetSortAsync(0, 9)
recommendedView = createGridSortView(UDim2.new(0, 1236, 0, 648), UDim2.new(0, 300, 0, 300), Vector2.new(12, 12),
2, 4, page, featuredTitle, featuredCollection)
end
recommendedView:SetPosition(currentPosition)
end
local loader = LoadingWidget(
{Parent = SortsContainer}, {loadGameSorts})
spawn(function()
loader:AwaitFinished()
loader:Cleanup()
loader = nil
populateSortsDebounce = false
if this.TransitionTweens == nil or #this.TransitionTweens == 0 then
this.TransitionTweens = ScreenManager:FadeInSitu(SelectableAvatarsContainer)
end
for i = 1, #sortsObjects do
sortsObjects[i]:SetVisible(true)
end
end)
end
local function UpdateLayout()
ProfileContainer.Size = Utility.CalculateRelativeDimensions(ProfileContainer, PROFILE_SIZE, MOCKUP_SIZE)
NameLabel.Size = Utility.CalculateRelativeDimensions(NameLabel, PROFILE_NAME_SIZE, MOCKUP_SIZE)
ProfileButton.Size = Utility.CalculateRelativeDimensions(ProfileButton, PROFILE_BUTTON_SIZE, MOCKUP_SIZE)
Utility.CalculateAnchor(ProfileButton, UDim2.new(0, 0, 1, -6), Utility.Enum.Anchor.BottomLeft)
ProfileImage.Size = Utility.CalculateRelativeDimensions(ProfileImage, PROFILE_IMAGE_SIZE, MOCKUP_SIZE)
AvatarBrushBackground.Size = UDim2.new(1 - ProfileImage.Size.X.Scale, -ProfileImage.Size.X.Offset, 1, 0)
-- AvatarBrushBackground.Size = Utility.CalculateRelativeDimensions(AvatarBrushBackground, PROFILE_AVATAR_BRUSH_SIZE, MOCKUP_SIZE)
AvatarBrushBackground.Position = UDim2.new(1 - AvatarBrushBackground.Size.X.Scale, AvatarBrushBackground.Size.X.Offset, 0, 0)
Utility.CalculateAnchor(AvatarBrushImage, UDim2.new(0.5, 0, 0.42, 0), Utility.Enum.Anchor.Center)
Utility.CalculateAnchor(AvatarTextLabel, UDim2.new(0.5, 0, 0.7, 0), Utility.Enum.Anchor.Center)
FriendActivityContainer.Size = UDim2.new(ProfileContainer.Size.X.Scale, 0, 0, 300);
FriendActivityContainer.Position = UDim2.new(0,0,ProfileContainer.Size.Y.Scale,0);
SortsContainer.Size = Utility.CalculateRelativeDimensions(SortsContainer, SORTS_SIZE, MOCKUP_SIZE)
SortsContainer.Position = UDim2.new(1 - SortsContainer.Size.X.Scale, 0, 0, 0)
end
UpdateLayout()
local screenResolutionChangedConn = nil
local profileButtonSelectedConn = nil
local profileButtonDeselectedConn = nil
function this:GetName()
return Strings:LocalizedString('HomeWord')
end
function this:IsFocused()
return inFocus
end
function this:GetDefaultSelectionObject()
return ProfileButton
end
function this:SetSelectionObject()
GuiService.SelectedCoreObject = self:GetDefaultSelectionObject()
-- TODO: Remember selection while staying in Pane. Pressing A on the Home tab should remember
-- last selection only while in pane.
end
function this:Show()
HomePaneContainer.Visible = true
PopulateSorts()
UpdateLayout()
Utility.DisconnectEvent(screenResolutionChangedConn)
screenResolutionChangedConn = GuiRoot.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
RunService.RenderStepped:wait()
UpdateLayout()
end
end)
Utility.DisconnectEvent(profileButtonSelectedConn)
Utility.DisconnectEvent(profileButtonDeselectedConn)
profileButtonSelectedConn = ProfileButton.SelectionGained:connect(OnProfileButtonSelectionChanged)
profileButtonDeselectedConn = ProfileButton.SelectionLost:connect(OnProfileButtonSelectionChanged)
OnProfileButtonSelectionChanged()
UpdateProfileInfo()
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = ScreenManager:DefaultFadeIn(HomePaneContainer)
delay(0.5, function()
if inFocus and GuiService.SelectedCoreObject == nil then
self:SetSelectionObject()
end
end)
ScreenManager:PlayDefaultOpenSound()
end
function this:Hide()
HomePaneContainer.Visible = false
profileButtonSelectedConn = Utility.DisconnectEvent(profileButtonSelectedConn)
profileButtonDeselectedConn = Utility.DisconnectEvent(profileButtonDeselectedConn)
screenResolutionChangedConn = Utility.DisconnectEvent(screenResolutionChangedConn)
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
end
function this:Focus()
inFocus = true
UpdateLayout()
self:SetSelectionObject()
OnProfileButtonSelectionChanged()
end
function this:RemoveFocus()
inFocus = false
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(HomePaneContainer) then
GuiService.SelectedCoreObject = nil
end
end
function this:SetPosition(newPosition)
HomePaneContainer.Position = newPosition
end
function this:SetParent(newParent)
HomePaneContainer.Parent = newParent
end
function this:IsAncestorOf(object)
return HomePaneContainer and HomePaneContainer:IsAncestorOf(object)
end
return this
end
return CreateHomePane
@@ -0,0 +1,764 @@
--[[
// Http.lua
// API for all web endpoints
// Calls are async
// Any calls to watrbx.wtf need to use game:HttpGetAynsc() and game:HttpPostAynsc()
// use rbxGetAsync() and rbxPostAsync()
// Any calls to api.watrbx.wtf should use HttpRbxApiService
// use rbxApiGetAsync() and rbxApiPostAsync()
// NOTE: You cannot currently get thumbnails with this API (please see the Thumbnail module), because
// Roblox GUIs cannot accept rbxcnd.com for the Image property.
]]
--[[ Services ]]--
local HttpService = game:GetService('HttpService')
local HttpRbxApiService = game:GetService('HttpRbxApiService')
local Http = {}
local BaseUrl = game:GetService('ContentProvider').BaseUrl:lower()
BaseUrl = string.gsub(BaseUrl, "/m.", "/www.")
-- TODO: There are some calls that fail when using https. Wait for web to fix.
BaseUrl = string.gsub(BaseUrl, "http://", "https://")
AssetGameBaseUrl = string.gsub(BaseUrl, "https://www.", "https://assetgame.")
Http.BaseUrl = BaseUrl
Http.AssetGameBaseUrl = AssetGameBaseUrl
--[[ Helper Functions ]]--
local function decodeJSON(json)
local success, result = pcall(function()
return HttpService:JSONDecode(json)
end)
if not success then
print("decodeJSON() failed because", result, "Input:", json)
return nil
end
return result
end
local function rbxGetAsync(path, returnRaw)
local success, result = pcall(function()
return game:HttpGetAsync(path)
end)
--
if not success then
print(path, "rbxGetAsync() failed because", result)
return nil
end
if returnRaw then
return result
end
return decodeJSON(result)
end
local function rbxPostAsync(path, params, contentType)
local success, result = pcall(function()
return game:HttpPostAsync(path, params, contentType)
end)
--
if not success then
print(path, "rbxPostAsync() failed because", result)
return nil
end
return decodeJSON(result)
end
local function rbxApiGetAsync(path, useHttps)
local success, result = pcall(function()
return HttpRbxApiService:GetAsync(path, useHttps)
end)
--
if not success then
print(path, "rbxApiGetAsync() failed because", result)
return nil
end
return decodeJSON(result)
end
local function rbxApiPostAsync(path, params, useHttps, throttlePriority, contentType)
local success, result = pcall(function()
return HttpRbxApiService:PostAsync(path, params, useHttps, throttlePriority, contentType)
end)
--
if not success then
print(path..params, "rbxApiPostAsync() failed because", result)
return nil
end
return decodeJSON(result)
end
--[[ Public API ]]--
--[[ Helper Functions ]]--
function Http.DecodeJSON(json)
return decodeJSON(json)
end
--[[ Games Endpoints ]]--
--[[
// Return Array of tables
// Table Keys
// Id - number
// Name - string
// TimeOptionsAvailable - boolean
// DefaultTimeOption - number
// GenresOptionsAvailable - boolean
]]
function Http.GetGameSortsAsync()
return rbxGetAsync(BaseUrl..'games/default-sorts')
end
--[[
All Sorts return the following json
// Returns Array of Tables
// Table Keys
// CreatorID - number
// CreatorName- string
// CreatorUrl - string
// Plays - number
// Price - number
// ProductID - number
// IsOwned - boolean
// IsVotingEnabled - boolean
// TotalUpVotes - number
// TotalDownVotes - number
// TotalBought - number
// UniverseID - number
// HasErrorOcurred - boolean
// Name - string
// PlaceID - number
// PlayerCount - number
// ImageId - number
]]
function Http.GetSortAsync(startRows, maxRows, sortId, timeFilter)
local path = string.format("%sgames/list-json?sortFilter=%d&StartRows=%d&MaxRows=%d&searchAllGames=false&filterByDeviceType=true",
BaseUrl, sortId, startRows, maxRows)
if timeFilter then
path = string.format("%s&timeFilter=%d", path, timeFilter)
end
return rbxGetAsync(path)
end
function Http.GetUserFavoritesAsync(startRows, maxRows)
local path = string.format("%sgames/moreresultsuncached-json?sortFilter=MyFavorite&StartRows=%d&MaxRows=%d&searchAllGames=true&filterByDeviceType=true",
BaseUrl, startRows, maxRows)
return rbxGetAsync(path)
end
function Http.GetUserRecentAsync(startRows, maxRows)
local path = string.format("%sgames/moreresultsuncached-json?sortFilter=MyRecent&StartRows=%d&MaxRows=%d&searchAllGames=true&filterByDeviceType=true",
BaseUrl, startRows, maxRows)
return rbxGetAsync(path)
end
function Http.GetUserPlacesAsync(startIndex, pageSize, userid)
local path = string.format("%sgames/list-users-games-json?userid=%d&startIndex=%d&pageSize=%d", BaseUrl, userid, startIndex, pageSize)
return rbxGetAsync(path)
end
-- Here for future use
function Http.SearchGamesAsync(startRows, maxRows, keyword)
local path = string.format("%sgames/list-json?keyword=%s&StartRows=%d&MaxRows=%d", BaseUrl, startRows, maxRows)
return rbxGetAsync(path)
end
--[[ Asset Endpoints ]]--
--[[
// Returns Table
// Table Keys
// Url - string
// Final - boolean
]]
function Http.GetAssetThumbnailFinalAsync(assetId, width, height, format)
return rbxGetAsync(AssetGameBaseUrl..'asset-thumbnail/json?assetId='..tostring(assetId)..'&width='..tostring(width)..
'&height='..tostring(height)..'&format='..format)
end
--[[
// See Above
]]
function Http.GetAssetAvatarFinalAsync(userId, width, height, format)
local result = rbxGetAsync(BaseUrl..'avatar-thumbnail/json?userId='..tostring(userId)..'&width='..tostring(width)..
'&height='..tostring(height)..'&format='..format)
return result
end
function Http.GetOutfitThumbnailFinalAsync(outfitId, width, height, format)
local requestData = {['userOutfitId'] = outfitId}
local requestDataJson = HttpService:JSONEncode(requestData)
local encodedRequestData = requestDataJson and HttpService:UrlEncode(requestDataJson) or ""
local url = string.format('%savatar-thumbnails?params=%s', BaseUrl, encodedRequestData)
print("Get outfit thumbnail url:", url)
local result = rbxGetAsync(url)
return result
end
--[[ Game Endpoints ]]--
--[[
// Return Table
// Table Keys
// AssetId - number
// Name - string
// Description - string
// Created - string date (m/dd/yyyy)
// Updated - string date (m/dd/yyyy)
// FavoritedCount - number
// Url - string url ex : "/games/192800/Work-at-a-Pizza-Place"
// ReportAbuseUrl - string url ex : "/abusereport/asset?id=192800&RedirectUrl=%2fgames%2f192800%2fWork-at-a-Pizza-Place",
// IsFavoritedByUser - boolean
// IsCreatedByUser - boolean
// VisitedCount - number
// MaxPlayers - number
// Builder - string
// BuilderId - number
// BuilderUrl - string url ex: "/User.aspx?ID=82471"
// IsPlayable - boolean
// ReasonProhibited - string
// ReasonProhibitedMessage - string
// IsBuildersClubOnly - boolean
// IsCopyLocked - boolean
// IsPersonalServer - boolean
// IsPersonalServerOverlay - boolean
// BuildersClubOverlay - string
// PlayButtonType - string (Enum?)
// AssetGenre - string
// AssetGenreViewModel - table
// DisplayName - string
// Id - number
// OnlineCount - number
// UniverseId - number
// UniverseRootPlaceId - number
// TotalUpVotes - number
// TotalDownVotes - number
// UserVote - null (?)
// OverridesDefaultAvatar - boolean
]]
function Http.GetGameDetailsAsync(placeId)
return rbxGetAsync(BaseUrl..'places/api-get-details?assetId='..tostring(placeId))
end
--[[
// Returns Table
// Table Keys
// Id - number
// PlaceId - number
// ImageId - number
// IconUrl - string
// IconFinal - boolean
// WikiUrl - string
// ReleaseDate - string
// IconUpdateSuccess - boolean
// IconUpdateMessage - string
]]
function Http.GetGameIconIdAsync(placeId)
return rbxGetAsync(BaseUrl..'places/icons/json?placeId='..tostring(placeId))
end
--[[
// Return Table
// Table Keys
// ShowVotes - boolean
// VotingModel - table
// ShowVotes - boolean
// UpVotes - number
// DownVotes - number
// CanVote - boolean
// UserVote - boolean / null if user has not voted on this game
// ReasonForNotVoteable - string
// InvalidAssetOrUser, EmailIsVerified, PlayGame
]]
function Http.GetGameVotesAsync(placeId)
return rbxGetAsync(BaseUrl..'PlaceItem/GameDetailsVotingPanelJson?placeId='..tostring(placeId))
end
--[[
// Returns Array of tables - Length should always be 6
// Table Keys
// PlaceId - number
// GameName - string
// GameSeoUrl - string
// Creator - table
// CreatorName - string
// CreatorTargetId - number
// CreatorType - number
// GameThumbnail - table
// Url - string
// IsFinal - boolean
// AssetId - number
// AssetType - number
// AssetHash - can be null
// ImageId - number, NEW
]]
function Http.GetRecommendedGamesAsync(currentPlaceId)
return rbxGetAsync(BaseUrl..'Games/GetRecommendedGamesJson?currentPlaceId='..tostring(currentPlaceId))
end
--[[
// Returns Table
// Table Keys
// IsMobile - boolean
// IsVideoAutoplayedOnReady - boolean
// thumbnailCount - number
// thumbnails - array of tables
// Tabke Keys
// AssetId - number
// AssetTypeId - number
// Url - string
// IsFinal - boolean
// AssetHash - can be null
]]
function Http.GetGameThumbnailsAsync(placeId)
return rbxGetAsync(BaseUrl..'thumbnail/place-thumbnails?placeId='..tostring(placeId))
end
--[[
// Returns Table
// Table Keys
// PlaceId - number
// GameBadges - array of tables
// BadgeAssetId - number
// IsOwned - boolean
// Rarity - number
// RarityName - string
// TotalAwarded - number
// TotalAwardedYesterday - number
// Created - string
// Updated - string
// BadgeSeoUrl - string
// CreatorId - number
// ImageUrl - string (DO NOT USE)
// IsImageUrlFinal - boolean
// Name - string
// Description - string
]]
function Http.GetGameBadgeDataAsync(placeId)
return rbxGetAsync(BaseUrl..'badges/list-badges-for-place/json?placeId='..tostring(placeId))
end
--[[
// Returns Table
// Table Keys
// PlaceId - number
// totalItems - number
// IsViewerPlaceOwner - boolean
// data - array of tables
// PassID - number
// PassName - string
// TotalSales - number
// PriceInRobux - number
// PriceInTickets - number
// Description - string
// UserOwns - boolean
// PassItemURL - string
// ProductID - number
// TotalUpVotes - number
// TotalDownVotes - number
// UserVote - null if user has not voted
// TotalFavorites - number
// IsFavoritedByUser - boolean
// PlaceOwnerId - number
// PlaceOwnerName - string
]]
function Http.GetGamePassesAsync(placeId, startIndex, maxRows)
return rbxGetAsync(BaseUrl..'Games/GetGamePassesPaged?placeId='..tostring(placeId)..
'&startIndex='..tostring(startIndex)..'&maxRows='..tostring(maxRows))
end
--[[
// Returns table
// Table Keys
// PlaceId - number
// totalItems - number
// IsViewerPlaceOwner - boolean
// data - array of tables
// Name - string
// Description - string
// PriceInRobux - number
// PriceInTickets - number
// ProductID - number
// AssetID - number
// TotalSales - number
// UserOwns - boolean
// SellerID - number
// SellerName - string
// ItemUrl - string
// IsRentable - boolean
// PromotionID - number
// BCRequirement - number
// IsForSale - boolean
// TotalUpVotes - number
// TotalDownVotes - number
// UserVote - boolean, can be null if user has not voted
// TotalFavorites - number
// IsFavoritedByUser - boolean
// AffiliateSalePlaceId - number
]]
function Http.GetPlaceProductsAsync(placeId, startIndex, maxRows)
return rbxGetAsync(BaseUrl..'Games/GetPlaceProductPromotions?placeId='..tostring(placeId)..
'&startIndex='..tostring(startIndex)..'&maxRows='..tostring(maxRows))
end
--[[
// Returns table
// Table Keys
// PlaceId - number
// TotalCollectionSize - number
// ShowShutdownAllButton - boolean
// Collection - Array of tables
// Table Keys
// PlaceId - number
// Capacity - number
// UserCanJoin - boolean
// ServerIpAddress - string
// Fps - number
// Guid - string
// JoinScript - string
// ShowSlowGameMessage - boolean
// Ping - number
// CurrentPlayers - array of tables
// Table Keys
// Id - number
// Username - string
// Thumbnail - table
// Table Keys
// AssetId - number
// AssetTypeId - number
// Url - string
// IsFinal - boolean
// AssetHash - can be null
]]
function Http.GetGameInstancesAsync(placeId, startIndex)
return rbxGetAsync(BaseUrl..'Games/GetGameInstancesJson?placeId='..tostring(placeId)..'&startIndex='..tostring(startIndex))
end
--[[
// Returns table
// Table Keys
// success - boolean
// message - string (is Whoa. Slow Down.)
]]
function Http.PostFavoriteToggleAsync(assetID)
return rbxPostAsync(BaseUrl..'favorite/toggle?assetID='..tostring(assetID), 'favoriteToggle')
end
-- TODO: Need to test flood check
--[[
// Returns Table
// Table Keys
// Success - boolean
// Model - table
// ModalType - string | Will be FloodCheckThresholdMet, PlayGame, EmailIsVerified
// Table Keys
// UserVote - boolean
// ShowVotes - boolean
// CanVote - boolean
// DownVotes - number
// UpVotes - number
]]
-- status can be true, false or null (null is a neutral vote)
function Http.PostGameVoteAsync(assetId, status)
return rbxPostAsync(BaseUrl..'voting/vote?assetId='..tostring(assetId)..'&vote='..tostring(status), 'vote')
end
--[[ Social ]]--
--[[
// Returns Table
// Table Keys
// GameId - number (can be null)
// IsOnline - boolean
// LastOnline - string
// LastLocation - string
// LocationType - number
// PlaceId - number (can be null)
]]
function Http.GetUserPresenceAsync(userId)
return rbxApiGetAsync('users/'..tostring(userId)..'/onlinestatus', true)
end
--[[
// Returns table
// Table Keys
// UserPresences - array of tables
// Table Keys
// VisitorId - number
// GameId - number or null
// IsOnline - boolean
// LastOnline - string
// LastLocation - string
// LocationType - number/enum
// PlaceId - number
]]
function Http.GetUsersOnlinePresenceAsync(listOfUsers)
--rbxApiPostAsync(path, params, useHttps, throttlePriority, contentType)
return rbxApiPostAsync('users/online-status', listOfUsers, true,
Enum.ThrottlingPriority.Default, Enum.HttpContentType.ApplicationJson)
end
--[[
// Returns Table
// Table Keys
// UserId - number
// TotalFriends - number
// CurrentPage - number
// PageSize - number
// TotalPages - number
// Friends - Array of Tables
// Table Keys
// UserId - number
// Username - string
// AvatarUri - string
// AvatarFinal - boolean
// InvitationId - number
// FriendshipStatus - number
// OnlineStatus - table
// Table Keys
// LocationOrLastSeen - string
// ImageUrl - string
// AlternateText - string
]]
function Http.GetFriendsAsync(userId, currentPage, pageSize)
return rbxGetAsync(BaseUrl..'friends/json?userId='..tostring(userId)..'&currentPage='..tostring(currentPage)..'&pageSize='..tostring(pageSize)..
'&friendsType=1')
end
--[[
// See Http.GetFriendsAsync
]]
function Http.GetRequestedFriendsAsync(userId, currentPage, pageSize)
return rbxGetAsync(BaseUrl..'friends/json?userId='..tostring(userId)..'&currentPage='..tostring(currentPage)..'&pageSize='..tostring(pageSize)..
'&friendsType=3')
end
--[[
// Returns array of tables
// Table Keys
// VisitorId - number
// GameId - string (hash)
// IsOnline - boolean
// LastOnline - string
// LastLocation - string
// LocationType - number (enum)
// PlaceId - number
]]
function Http.GetOnlineFriendsAsync()
return rbxApiGetAsync('my/friendsonline', true)
end
--[[
// Returns Table
]]
function Http.GetPlatformUserBalanceAsync()
return rbxApiGetAsync('my/platform-currency-budget', true)
end
function Http.GetTotalUserBalanceAsync()
return rbxApiGetAsync('currency/balance', true)
end
--[[
// Returns true if the user owns the asset, otherwise, returns false
]]
function Http.GetUserOwnsAssetAsync(userId, assetId)
-- local BaseUrl = 'http://api.sitetest2.pizzaboxer.fun/'
return rbxApiGetAsync(BaseUrl..'ownership/hasasset?userId='..tostring(userId)..'&assetId='..tostring(assetId), false)
end
--[[
// Returns Table
// Table Keys
// IsValid - boolean
// Data - Table of keys
// Table Keys
// TotalItems - number
// Start - number
// End - number
// Page - number
// Items - Array of tables
// Table Keys
// Item - Table
// Table Keys
// AssetId - number
// Name - string
// Creator - Table
// Table Keys
// Id - number
// Name - string
// Type - number
// Product - Table
// Table Keys
// PriceInRobux - number (nullable)
// PriceInTickets - - number (nullable)
// IsForSale - boolean
// IsPublicDomain - boolean
// IsResellable - boolean
// IsLimitedEdition - boolean
// IsUnique - boolean
// ExpireTime - time (nullable)
// IsExpired - boolean
// OTHER JUNK
"PrivateServer":null,
"Thumbnail": {"Final":true,"Url":"http://t4.watrbx.wtf/213527c418b9ea4fc7bbcfb79afd770f","RetryUrl":null}
},
]]
function Http.GetUserOwnedPackagesAsync(userId, currentPage)
currentPage = currentPage or 1
local packageAssetIdType = 32
-- return rbxGetAsync('http://www.sitetest2.pizzaboxer.fun/' ..'users/inventory/list-json?userId='..tostring(userId)..
-- '&assetTypeId='..tostring(packageAssetIdType)..'&pageNumber='..tostring(currentPage))
return rbxGetAsync(BaseUrl..'users/inventory/list-json?userId='..tostring(userId)..
'&assetTypeId='..tostring(packageAssetIdType)..'&pageNumber='..tostring(currentPage))
end
function Http.GetMyUserOutfitsAsync(startIndex, count)
startIndex = startIndex or 0
count = count or 20
local url = string.format('appearance/get-my-user-outfits?startIndex=%d&count=%d', startIndex, count)
return rbxApiGetAsync(url, true)
end
function Http.GetCharactersAssetsAsync(userId)
local url = string.format(AssetGameBaseUrl..'asset/characterfetch.ashx?userId=%d', userId)
return rbxGetAsync(url, true)
end
function Http.PostWearUserOutfitAsync(id)
local url = string.format('appearance/wear-user-outfit?id=%d', id)
return rbxApiPostAsync(url, '', false)
end
function Http.PostWearAssetAsync(assetId)
--local BaseUrl = 'http://api.gametest5.pizzaboxer.fun/'
return rbxApiPostAsync('appearance/set-clothing?assetIds='..tostring(assetId), '', false)
end
-- @ Params
-- productId - integer number of the id of the product (different from assetId)
-- expectedPrice - integer for how much of the currency is required to purchase
-- expectedSellerId - UserId integer for the seller of the product
-- expectedCurrency - integer denoting whether the currency is robux or tickets
-- 1 = robux
-- @ Example result is a table:
-- balanceAfterSale 197620
-- sl_translate title, errorMsg
-- AssetID 86500185
-- shortfallPrice -197620
-- errorMsg You already own this item.
-- statusCode 500
-- title Item Owned
-- currentPrice 25
-- expectedPrice 25
-- showDivID TransactionFailureView
-- expectedCurrency 1
-- currentCurrency 1
function Http.PurchaseProductAsync(productId, expectedPrice, expectedSellerId, expectedCurrency)
-- local formattedUrl = BaseUrl .. 'API/Item.ashx?rqtype=purchase' ..
-- '&productID=' .. tostring(productId) ..
-- '&expectedCurrency=' .. tostring(expectedCurrency) ..
-- '&expectedPrice=' .. tostring(expectedPrice) ..
-- '&expectedSellerID=' .. tostring(expectedSellerId)
local formattedUrl = string.format('%sAPI/Item.ashx?rqtype=purchase&productID=%d&expectedCurrency=%d&expectedPrice=%d&expectedSellerID=%d', BaseUrl, productId, expectedCurrency, expectedPrice, expectedSellerId)
print('PurchaseProductAsync:' , formattedUrl)
return rbxPostAsync(formattedUrl, '')
-- path, params, contentType)
-- return http://www.watrbx.wtf/API/Item.ashx?rqtype=purchase&productID=24805065&expectedCurrency=1&expectedPrice=88&expectedSellerID=1
end
-- Sample response:
--[=[
{Products = {{AssetTypeId = 32, IconImageAssetId = 0, IsNew = false, Updated = '2015-08-03T22:26:25.447Z', IsLimitedUnique = false, ProductId = 9187002, MinimumMembershipLevel = 1, Created = '2011-08-05T21:50:20.67Z', Creator = {Name = 'ROBLOX', Id = 1}, IsLimited = false, ContentRatingTypeId = 0, AssetId = 58537634, IsPublicDomain = false, Name = 'Knight of the Splintered Sky', IsForSale = true, Description = 'He fights bravely along side his brothers and the Knights of Redcliff to defeat the evil Korblox and their zombies.', PriceInRobux = 1000, Sales = 19}}}
--]=]
function Http.GetXboxProductsAsync(startIndex, count)
startIndex = startIndex or 0
count = count or 20
local url = string.format('xbox/catalog/contents?startIndex=%d&count=%d', startIndex, count)
return rbxApiGetAsync(url, true)
end
function Http.GetXboxCurrentlyWearingPackageAsync()
local url = string.format('xbox/currently-wearing')
return rbxApiGetAsync(url, true)
end
-----
function Http.GetThumbnailUrlForAsset(assetId, width, height)
width = width or 420
height = height or 420
return AssetGameBaseUrl .. 'Thumbs/Asset.ashx?width='..tostring(width)..'&height='..tostring(height)..'&assetId='..tostring(assetId)
end
-- Report Abuse
function Http.ReportAbuseAsync(reportingItemTypeName, reportingItemId, reportCategoryId, comment)
local jsonPostBody = {
reportingItemTypeName = reportingItemTypeName;
reportingItemId = tostring(reportingItemId);
reportCategoryId = tostring(reportCategoryId);
comment = comment;
}
local params = HttpService:JSONEncode(jsonPostBody)
if params then
return rbxApiPostAsync('moderation/reportabuse', params, true,
Enum.ThrottlingPriority.Default, Enum.HttpContentType.ApplicationJson)
end
end
--- Achievements
function Http.GetConsecutiveDaysLoggedInAsync()
local url = string.format('/xbox/get-login-consecutive-days')
return rbxApiGetAsync(url, true)
end
function Http.GetVoteCountAsync()
local url = string.format('/user/get-vote-count?targetType=Place')
return rbxApiGetAsync(url, true)
end
-- Account Linking
--[[
// Returns table
// Table Keys
// IsValid - boolean
// ErrorMessage - string
]]
function Http.IsValidUsername(username)
return rbxApiGetAsync('signup/is-username-valid?username='..username, true)
end
--[[
// Returns table
// Table Keys
// IsValid - boolean
// ErrorMessage - string
]]
function Http.IsValidPassword(username, password)
return rbxApiGetAsync('signup/is-password-valid?username='..username..'&password='..password, true)
end
return Http
@@ -0,0 +1,211 @@
--[[
// ImageOverlay.lua
// Creates an image overlay. Used with the game details page to see more thumbnails
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local createImageOverlay = function(thumbIds, selectedThumbIndex)
local this = {}
local thumbCount = #thumbIds
if selectedThumbIndex < 1 or selectedThumbIndex > thumbCount then
print("ImageOverlay: Invalid index to selectedThumbIndex")
return
end
local thumbnailImages = nil
local currentSelectedIndex = selectedThumbIndex
local baseZIndex = 3
local shield = Utility.Create'Frame'
{
Name = "Shield";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BackgroundColor3 = Color3.new(0, 0, 0);
BorderSizePixel = 0;
ZIndex = baseZIndex;
}
local container = Utility.Create'Frame'
{
Name = "ImageOverlayContainer";
Size = UDim2.new(1, 0, 0, 668);
Position = UDim2.new(0, 0, 0, 226);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.OverlayColor;
ZIndex = baseZIndex;
}
local dummySelectionImage = Utility.Create'TextButton'
{
Name = "DummySelectionImage";
Size = UDim2.new(0, 0, 0, 0);
Visible = false;
}
local imageSelection = Utility.Create'Frame'
{
Name = "imageSelection";
Size = UDim2.new(0, 1030, 0, 580);
Position = UDim2.new(0.5, -1030/2, 0.5, -580/2);
BackgroundTransparency = 1;
Selectable = true;
SelectionImageObject = dummySelectionImage;
Parent = container;
SoundManager:CreateSound('MoveSelection');
}
local leftArrowImage = Utility.Create'ImageButton'
{
Name = "LeftArrowImage";
Size = UDim2.new(0, 26, 0, 45);
Position = UDim2.new(0.5, imageSelection.Position.X.Offset - 18 - 75, 0.5, -45/2);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Settings/Slider/Left.png';
SelectionImageObject = dummySelectionImage;
ZIndex = baseZIndex + 1;
Parent = container;
SoundManager:CreateSound('MoveSelection');
}
local rightArrowImage = leftArrowImage:Clone()
rightArrowImage.Name = "RightArrowImage"
rightArrowImage.Position = UDim2.new(0.5, imageSelection.Position.X.Offset + imageSelection.Size.X.Offset + 75, 0.5, -45/2)
rightArrowImage.Image = 'rbxasset://textures/ui/Settings/Slider/Right.png';
rightArrowImage.Parent = container
local selectedText = Utility.Create'TextLabel'
{
Name = "SelectedText";
Size = UDim2.new();
Position = UDim2.new(0.5, 0, 1, -24);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = "";
ZIndex = baseZIndex;
Parent = container;
}
local function createThumbImages()
if not thumbnailImages then
thumbnailImages = {}
for i = 1, #thumbIds do
local image = Utility.Create'ImageLabel'
{
Name = tostring(i);
Size = imageSelection.Size;
Position = imageSelection.Position;
BackgroundTransparency = 1;
ZIndex = baseZIndex;
Parent = container;
SoundManager:CreateSound('MoveSelection');
}
local loader = ThumbnailLoader:Create(image, thumbIds[i],
ThumbnailLoader.Sizes.Large, ThumbnailLoader.AssetType.Icon, false)
spawn(function()
loader:LoadAsync(true, true, { ZIndex = image.ZIndex } )
end)
thumbnailImages[i] = image
end
end
end
local function setImagePositions(startIndex)
for i = 1, #thumbnailImages do
local image = thumbnailImages[i]
local xScale = i == startIndex and 0.5 or 1.5
image.Position = UDim2.new(xScale, -image.Size.X.Offset / 2, 0.5, image.Position.Y.Offset)
end
end
local function tweenImagePositions(currentIndex, nextIndex, direction)
if currentIndex == nextIndex then return end
local nextStartPosition = direction * 1.5
local currentEndPosition = -nextStartPosition
--
local currentImage = thumbnailImages[currentIndex]
local nextImage = thumbnailImages[nextIndex]
nextImage.Position = UDim2.new(nextStartPosition, -nextImage.Size.X.Offset / 2, 0.5, nextImage.Position.Y.Offset)
--
GuiService.SelectedCoreObject = imageSelection
Utility.TweenPositionOrSet(currentImage, UDim2.new(currentEndPosition, -currentImage.Size.X.Offset / 2, 0.5, currentImage.Position.Y.Offset),
Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.25, true)
Utility.TweenPositionOrSet(nextImage, UDim2.new(0.5, -nextImage.Size.X.Offset / 2, 0.5, nextImage.Position.Y.Offset),
Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.25, true)
selectedText.Text = tostring(nextIndex)..'/'..tostring(#thumbnailImages)
end
local function onArrowSelected(direction)
local nextIndex = nil
nextIndex = currentSelectedIndex + direction
if nextIndex < 1 then
nextIndex = thumbCount
elseif nextIndex > thumbCount then
nextIndex = 1
end
if nextIndex then
tweenImagePositions(currentSelectedIndex, nextIndex, direction)
currentSelectedIndex = nextIndex
end
end
--[[ Public API ]]--
function this:Show()
createThumbImages()
setImagePositions(currentSelectedIndex)
selectedText.Text = tostring(currentSelectedIndex).."/"..tostring(thumbCount)
if thumbCount == 1 then
leftArrowImage.Visible = false
rightArrowImage.Visible = false
end
shield.Parent = GuiRoot
container.Parent = shield.Parent
local shieldTweenIn = Utility.PropertyTweener(shield, "BackgroundTransparency", 1, 0.3, 0.25, Utility.EaseInOutQuad, nil)
end
function this:Hide()
local shieldTweenOut = Utility.PropertyTweener(shield, "BackgroundTransparency", 0.3, 1, 0.25, Utility.EaseInOutQuad, true, function()
shield:Destroy()
end)
container:Destroy()
SoundManager:Play('ScreenChange');
end
function this:Focus()
ContextActionService:BindCoreAction("CloseImageOverlay",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
end
end,
false, Enum.KeyCode.ButtonB)
leftArrowImage.SelectionGained:connect(function()
onArrowSelected(-1)
end)
rightArrowImage.SelectionGained:connect(function()
onArrowSelected(1)
end)
GuiService:AddSelectionParent("ImageOverlay", container)
GuiService.SelectedCoreObject = imageSelection
end
function this:RemoveFocus()
ContextActionService:UnbindCoreAction("CloseImageOverlay")
GuiService:RemoveSelectionGroup("ImageOverlay")
end
return this
end
return createImageOverlay
@@ -0,0 +1,240 @@
--[[
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function CreateImageSlider(size, position)
local this = {}
local items = {}
local maxItems = 0
local currentItemIndex = 1
local padding = 0
local focusSize = 450
local itemSize = 300
local dataTable = nil
local lastSelectedObject = nil
local newSelectedObjectCn = nil
this.OnNewFocusItem = Utility.Signal()
local imageObjectToLoaderMap = {}
this.Container = Utility.Create'ScrollingFrame'
{
Name = "ImageSliderContainer";
Size = size;
Position = position;
BackgroundTransparency = 1;
ClipsDescendants = false;
ScrollingEnabled = false;
Selectable = false;
ScrollBarThickness = 0;
}
local function loadNewImage(item, dataIndex)
if imageObjectToLoaderMap[item] then
imageObjectToLoaderMap[item]:Cancel()
imageObjectToLoaderMap[item] = nil
end
local iconId = dataTable[dataIndex].IconId
local thumbLoader = ThumbnailLoader:Create(item, iconId,
ThumbnailLoader.Sizes.Medium, ThumbnailLoader.AssetType.Icon)
imageObjectToLoaderMap[item] = thumbLoader
spawn(function()
thumbLoader:LoadAsync()
imageObjectToLoaderMap[item] = nil
end)
end
local MAX_LEFT = 2
local MAX_RIGHT = 6
-- TODO: Figure out left/right better
local function recalcPositionAndSize(tweenTime, isRight, newSelectedObject)
local smIconYPos = (focusSize - itemSize) / 2
local startPosition = 0
if currentItemIndex == 1 then
startPosition = 0
elseif currentItemIndex == 2 then
startPosition = -1
elseif maxItems == #items then
startPosition = 1 - currentItemIndex
elseif maxItems - currentItemIndex < MAX_RIGHT then
startPosition = maxItems - currentItemIndex - MAX_RIGHT - MAX_LEFT
else
startPosition = -MAX_LEFT
end
-- put front at back
if maxItems > #items then
if isRight == true and startPosition == -MAX_LEFT and currentItemIndex > 3 then
local front = items[1]
local back = items[#items]
front.Position = UDim2.new(0, back.Position.X.Offset + back.Size.X.Offset + padding, 0, smIconYPos)
this:RemoveFromFront()
this:AddItemToBack(front)
if dataTable and dataTable[currentItemIndex + MAX_RIGHT] then
loadNewImage(front, currentItemIndex + MAX_RIGHT)
end
-- put back in front
elseif isRight == false and currentItemIndex < maxItems - MAX_RIGHT and startPosition == -MAX_LEFT then
local back = items[#items]
local front = items[1]
back.Position = UDim2.new(0, front.Position.X.Offset - padding - itemSize, 0, smIconYPos)
this:RemoveFromBack()
this:AddToFront(back)
if dataTable and dataTable[currentItemIndex - MAX_LEFT] then
loadNewImage(back, currentItemIndex - MAX_LEFT)
end
end
end
local currentXPosition = itemSize * startPosition + padding * startPosition
for i = 1, #items do
if items[i] == newSelectedObject then
if tweenTime == 0 then
items[i].Size = UDim2.new(0, focusSize, 0, focusSize)
items[i].Position = UDim2.new(0, currentXPosition, 0, 0)
else
items[i]:TweenSizeAndPosition(UDim2.new(0, focusSize, 0, focusSize), UDim2.new(0, currentXPosition, 0, 0),
Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, tweenTime, true)
end
currentXPosition = currentXPosition + focusSize + padding
else
if tweenTime == 0 then
items[i].Size = UDim2.new(0, itemSize, 0, itemSize)
items[i].Position = UDim2.new(0, currentXPosition, 0, smIconYPos)
else
items[i]:TweenSizeAndPosition(UDim2.new(0, itemSize, 0, itemSize), UDim2.new(0, currentXPosition, 0, smIconYPos),
Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, tweenTime, true)
end
currentXPosition = currentXPosition + itemSize + padding
end
end
end
--[[ Events ]]--
newSelectedObjectCn = GuiService.Changed:connect(function(property)
if property == 'SelectedCoreObject' then
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject ~= lastSelectedObject and selectedObject:IsDescendantOf(this.Container) then
local isRight = nil
if lastSelectedObject then
-- slide left
if lastSelectedObject.AbsolutePosition.x < selectedObject.AbsolutePosition.x then
currentItemIndex = currentItemIndex + 1
isRight = true
-- slide right
else
currentItemIndex = currentItemIndex - 1
isRight = false
end
end
recalcPositionAndSize(lastSelectedObject and 0.25 or 0, isRight, selectedObject)
lastSelectedObject = selectedObject
this.OnNewFocusItem:fire(currentItemIndex, isRight, selectedObject)
end
end
end)
--[[ Public API ]]--
function this:SetPosition(position)
self.Container.Position = position
end
function this:SetSize(size)
self.Container.Size = size
end
function this:SetParent(parent)
self.Container.Parent = parent
end
function this:SetFocusPosition(index)
if index > 0 and index < #items + 1 then
currentItemIndex = index
recalcPositionAndSize(0, nil, items[currentItemIndex])
end
end
function this:SetDataTable(tbl)
dataTable = tbl
end
function this:SetMaxItems(value)
local index = nil
-- we're near the end, so we need to update image content
if currentItemIndex > maxItems - MAX_RIGHT then
-- find the current selected index in relation to image pool
for i = 1, #items do
if lastSelectedObject and items[i] == GuiService.SelectedCoreObject then
index = i
break
end
end
-- TODO: Still might be a visual bug in some cases
if index then
-- replace images from front of pool
for i = 1, index - 3 do
if dataTable[currentItemIndex + i] then
local item = items[1]
loadNewImage(item, currentItemIndex + (maxItems - currentItemIndex) + i)
print("NEW IMAGE:", currentItemIndex, ":", maxItems, ":", i, ":", currentItemIndex + (maxItems - currentItemIndex) + i)
self:RemoveFromFront()
self:AddItemToBack(item)
end
end
end
end
maxItems = value
recalcPositionAndSize(0)
end
function this:SetPadding(newPadding)
padding = newPadding
end
function this:AddToFront(newItem)
table.insert(items, 1, newItem)
newItem.Parent = this.Container
end
function this:AddItemToBack(newItem)
if this and this.Container then
items[#items + 1] = newItem
newItem.Parent = this.Container
end
end
function this:AddItem(newItem)
self:AddItemToBack(newItem)
end
function this:RemoveFromFront()
table.remove(items, 1)
end
function this:RemoveFromBack()
table.remove(items)
end
function this:Destroy()
if newSelectedObjectCn then
newSelectedObjectCn:disconnect()
newSelectedObjectCn = nil
end
this.Container:Destroy()
this.OnNewFocus = nil
this = nil
end
return this
end
return CreateImageSlider
@@ -0,0 +1,147 @@
--[[
// LinkAccountScreen.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService('ContextActionService')
local GuiService = game:GetService('GuiService')
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseSignInScreen = require(Modules:FindFirstChild('BaseSignInScreen'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local TextBox = require(Modules:FindFirstChild('TextBox'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function createLinkAccountScreen()
local this = BaseSignInScreen()
this:SetTitle(string.upper(Strings:LocalizedString("LinkAccountTitle")))
this:SetDescriptionText(Strings:LocalizedString("LinkAccountPhrase"))
local ModalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = GlobalSettings.ModalBackgroundTransparency;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local myUsername = nil
local myPassword = nil
this.UsernameObject:SetDefaultText(Strings:LocalizedString("UsernameWord"))
this.UsernameObject:SetKeyboardTitle(Strings:LocalizedString("UsernameWord"))
local usernameChangedCn = nil
this.PasswordObject:SetDefaultText(Strings:LocalizedString("PasswordWord"))
this.PasswordObject:SetKeyboardTitle(Strings:LocalizedString("PasswordWord"))
this.PasswordObject:SetKeyboardType(Enum.XboxKeyBoardType.Password)
local passwordChangedCn = nil
local function linkAccountAsync()
local linkResult = nil
local signInResult = nil
local loader = LoadingWidget(
{ Parent = this.Container }, {
-- try link account
function()
linkResult = AccountManager:LinkAccountAsync(myUsername, myPassword)
-- sign in here on success
if linkResult == AccountManager.AuthResults.Success then
signInResult = AccountManager:SignInAsync(Enum.UserInputType.Gamepad1)
end
end
})
-- set up full screen loader
ModalOverlay.Parent = GuiRoot
ContextActionService:BindCoreAction("BlockB", function() end, false, Enum.KeyCode.ButtonB)
local selectedObject = GuiService.SelectedCoreObject
GuiService.SelectedCoreObject = nil
-- call loader
loader:AwaitFinished()
-- clean up
loader:Cleanup()
loader = nil
GuiService.SelectedCoreObject = selectedObject
ContextActionService:UnbindCoreAction("BlockB")
ModalOverlay.Parent = nil
if linkResult ~= AccountManager.AuthResults.Success then
local err = linkResult and Errors.Authentication[linkResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
else
if signInResult == AccountManager.AuthResults.Success then
ScreenManager:CloseCurrent()
EventHub:dispatchEvent(EventHub.Notifications["AuthenticationSuccess"])
else
local err = signInResult and Errors.Authentication[signInResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
end
end
local isSigningIn = false
this.SignInButton.MouseButton1Click:connect(function()
if isSigningIn then return end
isSigningIn = true
SoundManager:Play('ButtonPress')
if (myUsername and #myUsername > 0) and (myPassword and #myPassword > 0) then
linkAccountAsync()
else
local err = Errors.SignIn.NoUsernameOrPasswordEntered
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
isSigningIn = false
end)
--[[ Public API ]]--
--override
local baseFocus = this.Focus
function this:Focus()
baseFocus(self)
usernameChangedCn = this.UsernameObject.OnTextChanged:connect(function(text)
myUsername = text
if #myUsername > 0 then
GuiService.SelectedCoreObject = this.PasswordSelection
else
GuiService.SelectedCoreObject = this.UsernameSelection
end
end)
passwordChangedCn = this.PasswordObject.OnTextChanged:connect(function(text)
myPassword = text
if #myPassword > 0 then
GuiService.SelectedCoreObject = this.SignInButton
else
GuiService.SelectedCoreObject = this.PasswordSelection
end
end)
end
--override
local baseRemoveFocus = this.RemoveFocus
function this:RemoveFocus()
baseRemoveFocus(self)
Utility.DisconnectEvent(usernameChangedCn)
Utility.DisconnectEvent(passwordChangedCn)
end
return this
end
return createLinkAccountScreen
@@ -0,0 +1,89 @@
--[[
// LoadingWidget.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local Utility = require(Modules:FindFirstChild('Utility'))
local function CreateLoadingWidget(properties, loadingFunctions)
properties = properties or {}
loadingFunctions = loadingFunctions or {}
local this = {}
local completedFunctions = {}
local cancelled = false
local finishedConn = Utility.Signal()
local loadIcon = Utility.Create'ImageLabel'
{
Name = "LoadIcon";
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/LoadingSpinner@1080.png';
Size = properties.Size or UDim2.new(0,99,0,100);
ZIndex = properties.ZIndex or 7;
Parent = properties.Parent;
}
Utility.CalculateAnchor(loadIcon, properties.Position or UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
if properties.Visible == false then
loadIcon.Visible = false
end
local function isLoadingComplete()
return #completedFunctions == #loadingFunctions
end
function this:AwaitFinished()
if isLoadingComplete() then
return true
end
finishedConn:wait()
if cancelled then
return false
end
return true
end
function this:Cleanup()
loadIcon.Parent = nil
loadIcon:Destroy()
cancelled = true
end
-- Run it!
for _, loadingFunction in pairs(loadingFunctions) do
spawn(function()
loadingFunction()
table.insert(completedFunctions, loadingFunction)
end)
end
spawn(function()
local t = tick()
while not (cancelled or isLoadingComplete()) do
local now = tick()
local rotation = (now - t) * 360
if loadIcon.Parent then
loadIcon.Rotation = loadIcon.Rotation + rotation
end
t = now
wait()
end
finishedConn.fire()
end)
return this
end
return CreateLoadingWidget
@@ -0,0 +1,275 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local enUS =
{
["HomeWord"] = "Home";
["GameWord"] = "Games";
["FriendsWord"] = "Friends";
["CatalogWord"] = "ROBUX";
["AvatarWord"] = "Avatar";
["PlayWord"] = "Play";
["FavoriteWord"] = "Favorite";
["FavoritedWord"] = "Favorited";
["LikedWord"] = "Liked";
["DislikedWord"] = "Disliked";
["BackWord"] = "Back";
["SettingsWord"] = "Settings";
["AccountWord"] = "Account";
["OverscanWord"] = "Adjust Screen";
["HelpWord"] = "Help";
["SwitchProfileWord"] = "Switch Profile";
["SearchWord"] = "Search";
["EditAvatarPhrase"] = "Edit Avatar";
["FriendActivityWord"] = "Friend Activity";
["OnlineFriendsWords"] = "Online Friends";
["RecentlyPlayedWithSortTitle"] = "Recently Played With";
["StartPartyPhrase"] = "Snap Party App";
["RecommendedSortTitle"] = "Recommended";
["FavoritesSortTitle"] = "Favorites";
["RecentlyPlayedSortTitle"] = "Recently Played";
["MyRecentTitle"] = "My Recent";
["TopEarningTitle"] = "Top Earning";
["TopRatedTitle"] = "Top Rated";
["PopularTitle"] = "Popular";
["FeaturedTitle"] = "Featured";
["MoreGamesPhrase"] = "More Games";
["NoFriendsOnlinePhrase"] = "Your friends are not online";
["JoinGameWord"] = "Join Game";
["ViewGameDetailsWord"] = "View Game Details";
["InviteToPartyWord"] = "Invite To Party";
["ViewGamerCardWord"] = "View Gamer Card";
["RatingDescriptionTitle"] = "Rating and Description";
["GameImagesTitle"] = "Game Images";
["GameBadgesTitle"] = "Game Badges";
["FriendActivityTitle"] = "Friend Activity";
["RelatedGamesTitle"] = "Related Games";
["MoreDetailsTitle"] = "More Details";
["GameBadgesTitle"] = "Game Badges";
["SelectYourAvatarTitle"] = "Select Your Avatar";
["LastUpdatedWord"] = "Last Updated";
["CreationDateWord"] = "Creation Date";
["CreatedByWord"] = "Created By";
["ReportGameWord"] = "Report Game";
["HaveBadgeWord"] = "You Have This Badge";
["MaxPlayersWord"] = "Max Players";
["ScreenSizeWord"] = "Screen Size";
["ResizeScreenPrompt"] = "Use the right stick to adjust the edges of the white box until it is just off the screen";
["ResizeScreenInputHint"] = "Resize Screen";
["AcceptWord"] = "Accept";
["ResetWord"] = "Reset";
["CannotVoteWord"] = "You must play this game before rating";
["FirstToRateWord"] = "Be the first to rate this game";
["CustomAvatarPhrase"] = "This game uses custom Avatars";
["EngagementScreenHint"] = "Press Any Button To Begin";
["EquipWord"] = "Swap Avatar";
["BuyWord"] = "Buy";
["OkWord"] = "Ok";
["FreeWord"] = "Free";
["TakeWord"] = "Take";
['GetRobuxPhrase'] = 'Get ROBUX';
["AlreadyOwnedPhrase"] = "You already own this";
["PurchasedThisPhrase"] = 'You purchased this for %s ROBUX';
["RobuxBalanceTitle"] = 'My Balance';
["RobuxBalanceOverlayTitle"] = "ROBUX Balance";
["RobuxBalanceOverlayPhrase"] = "Only ROBUX purchased from the Xbox Store may be used on Xbox.";
["PlatformBalanceTitle"] = "Available on Xbox:";
["TotalBalanceTitle"] = "Total Balance:";
["AvatarCatalogTitle"] = 'Catalog';
["AvatarOutfitsTitle"] = 'My Collection';
["PurchasingTitle"] = 'Purchasing...';
["ConfirmPurchaseTitle"] = 'Confirm Purchase';
["AreYouSurePhrase"] = 'Are you sure you want to buy "%s"?';
["AreYouSureTakePhrase"] = 'Are you sure you want to take "%s"?';
["AreYouSureWithPricePhrase"] = 'Are you sure you want to buy "%s" for %s?';
["RemainingBalancePhrase"] = 'Your remaining balance will be %s ROBUX';
["ConfirmWord"] = 'Confirm';
["DeclineWord"] = 'Decline';
["OnlineWord"] = "Online";
["OfflineWord"] = "Offline";
["SubmitWord"] = "Submit";
["ReportPhrase"] = "You can send a report to our moderation team. We will review the game and take appropriate action.";
["CurrencySymbol"] = "$";
["RobuxStoreDescription"] = "Get ROBUX to buy great new looks for your Avatar, plus perks and abilities in games.";
["RobuxStoreNoItemsPhrase"] = "There are no items for purchase right now, please try again later";
["PercentMoreRobuxPhrase"] = "%s%% More";
--["RobuxStoreError"] = "ROBUX items are inaccessable at this time.";
["NoFriendsPhrase"] = "Your friends are not online. Play some games and make new friends!";
-- Platform Service Errors
["PopupPartyUIErrorPhrase"] = "There was an error trying to start a party. Please try again.";
-- Auth Errors
["AuthenticationErrorTitle"] = "Authentication Error";
["AuthInProgressPhrase"] = "Authentication already in progress";
["AuthAccountUnlinkedPhrase"] = ""; -- This is a special case handled by EngagementScreen
["AuthMissingGamePadPhrase"] = "No gamepad detected. Please turn on a gamepad.";
["AuthNoUserDetectedPhrase"] = "No Xbox Live user detected. Please sign in to an Xbox Live account.";
["AuthHttpErrorDetected"] = "Trouble communicating with ROBLOX servers. Please check www.watrbx.wtf/help/xbox for more info.";
["AuthErrorPhrase"] = "Trouble communicating with ROBLOX servers. Please try again.";
-- Reauth (Booted to engagement screen)
["ReauthSignedOutTitle"] = "Signed Out";
["ReauthSignedOutPhrase"] = "You have signed out of your Xbox Live account. Please sign in to continue.";
["ReauthRemovedTitle"] = "User Changed";
["ReauthRemovedPhrase"] = "We have detected a change in the active user. Please sign in again.";
["ReauthInvalidSessionPhrase"] = "You have been signed out of all current ROBLOX sessons.";
["ReauthUnlinkTitle"] = "Account Unlinked";
["ReauthUnlinkPhrase"] = "You have successfully unlinked from your ROBLOX account. You can now choose to sign in again, or sign up for a new ROBLOX account.";
["ReauthUnknownPhrase"] = "An error occurred and you have been signed out. Please sign in again to continue playing.";
-- Sign In
["NewUserPhrase"] = "Community Created Gaming. Limitless Possibilities.";
["SignInPhrase"] = "Sign In";
["PlayAsPhrase"] = "Sign Up Using %s";
["UsernameWord"] = "Username";
["PasswordWord"] = "Password";
["UsernameRulePhrase"] = "3-20 characters, no spaces";
["PasswordRulePhrase"] = "6 letters and 2 numbers minimum";
["AccountSettingsTitle"] = "Account Settings";
["PlatformLinkInfoTitle"] = "Welcome to ROBLOX!";
["PlatformLinkInfoMessage"] = "We have created a new ROBLOX account for you. All game progress and purchases will be saved to this ROBLOX account. Now you can sign in on any platform and continue where you left off!";
-- Sign In/Up Errors
["InvalidUsernameTitle"] = "Invalid Username";
["InvalidPasswordTitle"] = "Invalid Password";
["InvalidUsernamePhrase"] = "Usernames must have 3-20 characters.";
["InvalidPasswordPhrase"] = "Passwords must have at least 6 letters and 2 numbers";
["AlreadyTakenTitle"] = "Username Taken";
["AlreadyTakenPhrase"] = "That username is already taken, please try another.";
["InvalidCharactersUsedPhrase"] = "The username you entered contains invalid characters. Only letters and numbers are allowed.";
["UsernameCannotContainSpacesPhrase"] = "The username you entered contains spaces. Only letters and numbers are allowed.";
["NoUsernameEnteredPhrase"] = "Username is required.";
["NoUsernameOrPasswordEnteredPhrase"] = "You must enter a valid username and password to set up your ROBLOX account.";
["LinkedAsPhrase"] = "%s is currently linked to your ROBLOX account, %s.";
-- Linking Errors
["LinkSignUpDisabled"] = "Sign up is currently disabled. Please try again later.";
["LinkFlooded"] = "You have signed up too many times today. Please sign in with an existing ROBLOX account or try again later.";
["LinkLeaseLocked"] = "Transaction in progress. Please wait.";
["LinkAccountLinkingDisabled"] = "Account linking is currently disabled. Please try again later.";
["LinkInvalidRobloxUser"] = "The ROBLOX username you entered is invalid. Please enter a valid ROBLOX username.";
["LinkRobloxUserAlreadyLinked"] = "Your Xbox Live account is already linked to a ROBLOX account";
["LinkXboxUserAlreadyLinked"] = "Your Xbox Live account is already linked to a ROBLOX account.";
["LinkIllegalChildAccountLinking"] = "The accounts could not be linked. Please review your Xbox age settings.";
["LinkInvalidPassword"] = "The password you entered is invalid. Please enter the correct password.";
["LinkUsernamePasswordNotSet"] = "Username or password is empty. Please enter a username and password and try again.";
["LinkUsernameAlreadyTaken"] = "That username is already taken. Please choose another username and try again.";
["LinkInvalidCredentials"] = "The username or password is invalid. Please enter a valid username and password and try again.";
["LinkUnknownError"] = "An unknown error has occurred. Please try again.";
-- Linking
["LinkAccountTitle"] = "Sign in to ROBLOX";
["LinkAccountPhrase"] = "Sign in with an existing ROBLOX account to access your Avatar appearance and save game progress.\n\nYour ROBLOX account will be linked to your Xbox Live account, and the next time you play you will sign in automatically!\n\nYou can unlink from the Settings screen at any time.";
-- Sign Up
["SignUpWord"] = "Sign up";
["SignUpTitle"] = "Create a ROBLOX Account";
["SignUpPhrase"] = "A ROBLOX account is your access to ROBLOX on every platform. Simply set a username and password and you can sign in anywhere and continue where you left off!\n\nYour new ROBLOX account will be linked to your Xbox Live account, and the next time you play you will sign in automatically!\n\nYou can unlink from the Settings screen at any time.";
-- Edge Case - have linked account but no credentials
["SetCredentialsTitle"] = "Assign Username and Password";
["SetCredentialsPhrase"] = "Looks like you were interrupted before you could finish signing up! Don't worry, you can finish setting up your ROBLOX account by choosing a username and password here.\n\nYour ROBLOX account will be linked to your Xbox Live account, and the next time you play you will sign in automatically!\n\nYou can unlink from the Settings screen at any time.";
["SetCredentialsWord"] = "Assign";
-- Unlink
["UnlinkTitle"] = "Unlink Account";
["UnlinkGamerTagPhrase"] = "Unlink %s";
["UnlinkPhrase"] = "Are you sure you want to unlink this ROBLOX account from your Xbox account? Your save data and purchases are associated with this ROBLOX account and will be inaccessible until you sign back in!";
-- Error Strings
["UnableToJoinTitle"] = "Unable to Join";
["ErrorOccurredTitle"] = "An Error Occurred";
["DefaultErrorPhrase"] = "Could not connect to ROBLOX. Please try again later.";
["DefaultJoinFailPhrase"] = "Could not connect to ROBLOX. Please try again later.";
["AlreadyRunningPhrase"] = "You are already in this ROBLOX game.";
["WebServerConnectFailPhrase"] = "Could not connect to ROBLOX servers. Please try again later.";
["AccessDeniedByWeb"] = "The ROBLOX game you are trying to join is currently not available.";
["InstanceNotFound"] = "The ROBLOX game you are trying to join is currently not available.";
["GameFullPhrase"] = "The ROBLOX game you are trying to join is currently full.";
["FollowUserFailed"] = "The ROBLOX game you are trying to join is currently not available.";
["UnableToEquipTitle"] = "Unable to Select";
["UnableToEquipPhrase"] = "We were unable to select that Avatar. Please try again later.";
["UnableToWearOufitTitle"] = "Unable to Select";
["UnableToWearOufitPhrase"] = "We were unable to select that Outfit. Please try again later.";
["UnableToDoPurchaseTitle"] = "Unable to Complete Purchase";
["UnableToDoPurchasePhrase"] = "We were unable to complete your purchase. Please try again later.";
["UnableToDoRobuxPurchaseTitle"] = "Unable to Complete Purchase";
["UnableToDoRobuxPurchasePhrase"] = "We were unable to complete your purchase. Please try again later.";
["CannotVoteTitle"] = "Cannot Vote";
["VoteFloodPhrase"] = "You're voting too often. Come back later and try again.";
["VotePlayGamePhrase"] = "You must play this game before voting.";
["CannotFavoriteTitle"] = "Cannot Favorite Game";
["FavoriteFloodPhrase"] = "You're favoriting games too often. Come back later and try again.";
-- Controller connection errors
["ControllerLostConnectionTitle"] = "Missing Controller";
["ControllerLostConnectionPhrase"] = "Controller for user '%s' has been disconnected, please press 'A' on the controller you would like to continue with.";
["ActiveUserLostConnectionTitle"] = "Active User Removed";
["ActiveUserLostConnectionPhrase"] = "User '%s' has been logged out, please press 'A' on the controller you would like to continue with.";
-- Terms of Service & Privacy
["ToSPhrase"] = "View Terms...";
["ToSInfoLinkPhrase"] = "Terms & Privacy Policy:\nwww.watrbx.wtf/info/terms-of-service\nwww.watrbx.wtf/info/Privacy.aspx";
["PrivacyPhrase"] = "Privacy";
-- Codes
["VersionIdString"] = "Version: %s.%s.%s.%s";
["ErrorMessageAndCodePrase"] = "%s\nError Code: %d";
-- Play My Place
["PlayMyPlaceMoreGamesTitle"] = "My Games";
["PlayMyPlaceMoreGamesPhrase"] = "Sign in on watrbx.wtf to create more games and edit your existing creations!";
["PrivateSessionPhrase"] = "In Private Game";
}
local this = {}
function this:GetLocale()
return enUS
end
function this:LocalizedString(stringKey)
local locale = self:GetLocale()
local result = locale and locale[stringKey]
if not result then
print("LocalizedString: Could not find string for:" , stringKey , "using locale:" , locale)
result = stringKey
end
return result
end
return this
@@ -0,0 +1,120 @@
--[[
// NoActionOverlay.lua
// Creates an overlay where the user cannot take an action
// to remove.
// This is used when we detect something wrong with input or the active user
// being lost
]]
local DATAMODEL_TYPE = {
APP_SHELL = 0;
GAME = 1;
}
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local createNoActionOverlay = function(errorType)
local this = BaseOverlay()
local title = errorType.Title
local message = errorType.Msg
local errorCode = errorType.Code
local iconImage = Utility.Create'ImageLabel'
{
Name = "IconImage";
Size = UDim2.new(0, 416, 0, 416);
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex;
Image = 'rbxasset://textures/ui/Shell/Icons/AlertIcon.png';
}
Utility.CalculateAnchor(iconImage, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
this:SetImage(iconImage)
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, this.RightAlign, 0, 136);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = title;
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local descriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 762, 0, 304);
Position = UDim2.new(0, this.RightAlign, 0, titleText.Position.Y.Offset + 62);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = message;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
if errorCode then
descriptionText.Text = string.format(Strings:LocalizedString('ErrorMessageAndCodePrase'), message, errorCode)
end
function this:GetPriority()
return GlobalSettings.ImmediatePriority
end
--[[ Public API ]]--
-- override
function this:Focus()
-- DO NOTHING
end
function this:RemoveFocus()
-- DO NOTHING
end
local baseShow = this.Show
function this:Show()
ContextActionService:BindCoreAction("StopControllerInput", function() end, false, Enum.UserInputType.Gamepad1)
baseShow(self)
end
local baseHide = this.Hide
function this:Hide()
baseHide(self)
ContextActionService:UnbindCoreAction("StopControllerInput")
end
local baseFocus = this.Focus
function this:Focus()
baseFocus()
-- NOTE: This might want to be:
-- `not PlatformService or`
if PlatformService and PlatformService.DatamodelType == DATAMODEL_TYPE.APP_SHELL then
GuiService.SelectedCoreObject = nil
end
end
return this
end
return createNoActionOverlay
@@ -0,0 +1,148 @@
--[[
// OutfitData.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local UserData = require(Modules:FindFirstChild('UserData'))
local Http = require(Modules:FindFirstChild('Http'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local OutfitData = {}
local wearingOutfit = nil
local function CreateOutfitItem(outfitInfo)
local this = {}
local isWearing = false
this.IsWearingChanged = Utility.Signal()
function this:GetUserId()
return outfitInfo['UserId']
end
function this:GetOutfitId()
return outfitInfo['OutfitId']
end
function this:GetId()
return outfitInfo['Id']
end
function this:GetName()
return outfitInfo['Name']
end
function this:IsOwned()
return true
end
function this:IsWearing()
local wasWearing = isWearing
isWearing = (self:GetId() == wearingOutfit)
if isWearing ~= wasWearing then
self.IsWearingChanged:fire(isWearing)
end
return isWearing
end
function this:WearAsync()
local result = Http.PostWearUserOutfitAsync(self:GetId())
-- if result and result['success'] == true then
wearingOutfit = self:GetId()
EventHub:dispatchEvent(EventHub.Notifications["DonnedDifferentOutfit"], self:GetId())
-- end
return
end
return this
end
local OutfitCache = nil
local RbxUid = nil
local debounceGetGetMyOutfitsAsync = false
function OutfitData:GetMyOutfitsAsync()
while debounceGetGetMyOutfitsAsync do wait() end
debounceGetGetMyOutfitsAsync = true
UserData.GetLocalPlayerAsync()
if RbxUid ~= UserData:GetRbxUserId() then
OutfitCache = nil
end
while not OutfitCache do
local startRbxUid = UserData:GetRbxUserId()
local outfits = {}
local index = 0
local count = 20
repeat
local result = nil
Utility.ExponentialRepeat(
function() return result == nil end,
function() result = Http.GetMyUserOutfitsAsync(index, count) end,
5)
if result then
local userOutfits = result['UserOutfits']
if userOutfits then
for _, outfitInfo in pairs(userOutfits) do
table.insert(outfits, CreateOutfitItem(outfitInfo))
end
end
end
index = index + count
until result == nil or result['FinalPage']
local nowRbxUid = UserData:GetRbxUserId()
if startRbxUid == nowRbxUid then
OutfitCache = outfits
end
print("Getting info cache again" , "now" , nowRbxUid , "startRbxUid" , startRbxUid)
end
debounceGetGetMyOutfitsAsync = false
return OutfitCache
end
function OutfitData:GetCachedWearingOutfitId()
return wearingOutfit
end
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentPackage"], "OutfitData",
function()
wearingOutfit = nil
if OutfitCache then
for _, outfit in pairs(OutfitCache) do
outfit:IsWearing()
end
end
end)
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentOutfit"], "OutfitData",
function()
if OutfitCache then
for _, outfit in pairs(OutfitCache) do
outfit:IsWearing()
end
end
end)
return OutfitData
@@ -0,0 +1,107 @@
--[[
// OutfitTile.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local ContextActionService = game:GetService("ContextActionService")
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local BaseTile = require(Modules:FindFirstChild('BaseTile'))
local function createOutfitTileContainer(outfitData)
local this = BaseTile()
local function wearOutfitAsync()
local result = outfitData:WearAsync()
-- print("Wear Outfit Result:" , Utility.PrettyPrint(result))
-- if result and result['success'] == true then
-- else
-- local err = Errors.OutfitEquip['Default']
-- ScreenManager:OpenScreen(ErrorOverlayModule(err), false)
-- end
end
local thumbnailLoader = ThumbnailLoader:Create(this.AvatarImage, outfitData:GetOutfitId(), ThumbnailLoader.Sizes.Medium, ThumbnailLoader.AssetType.Outfit, true)
spawn(function()
thumbnailLoader:LoadAsync(false, true)
end)
this:SetPopupText(outfitData:GetName())
function this:UpdateEquipButton()
self.EquippedCheckmark.Visible = outfitData:IsWearing()
end
function this:GetPackageInfo()
return outfitData
end
local selectDebounce = false
function this:Select()
if selectDebounce then return false end
selectDebounce = true
spawn(function()
wearOutfitAsync()
selectDebounce = false
end)
return true
end
local isWearingConn = nil
local baseShow = this.Show
function this:Show()
baseShow(self)
self:SetActive(true)
Utility.DisconnectEvent(isWearingConn)
isWearingConn = outfitData.IsWearingChanged:connect(function() self:UpdateEquipButton() end)
end
local baseHide = this.Hide
function this:Hide()
baseHide(self)
isWearingConn = Utility.DisconnectEvent(isWearingConn)
end
local baseFocus = this.Focus
function this:Focus()
baseFocus(self)
-- ContextActionService:BindCoreAction("WearSelectedOutfitItem",
-- function(actionName, inputState, inputObject)
-- if inputState == Enum.UserInputState.End then
-- SoundManager:Play('ButtonPress')
-- wearOutfitAsync()
-- end
-- end,
-- false,
-- Enum.KeyCode.ButtonX)
end
local baseRemoveFocus = this.RemoveFocus
function this:RemoveFocus()
baseRemoveFocus(self)
-- ContextActionService:UnbindCoreAction("WearSelectedOutfitItem")
end
return this
end
return createOutfitTileContainer
@@ -0,0 +1,450 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local TextService = game:GetService('TextService')
local CoreGui = game:GetService("CoreGui")
local GuiService = game:GetService('GuiService')
local RunService = game:GetService('RunService')
local ContextActionService = game:GetService("ContextActionService")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local GameOptionsSettings = settings():FindFirstChild("Game Options")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local BACKGROUND_COLOR = Color3.new(3/255,3/255,3/255)
local START_EDGE_SIZE = UDim2.new(0.9,0,0.9,0)
local MIN_EDGE_SIZE = UDim2.new(0.85,0,0.85,0)
local MAX_EDGE_SIZE = UDim2.new(1,0,1,0)
local ZERO_VEC2 = Vector2.new(0,0)
local THUMBSTICK_BORDER_SENSITIVITY = 0.05
local MAX_STICK_ACCELERATION = 3
local ACCELERATION_RATE = 1
local DPAD_STEP_AMOUNT = 2
local DPAD_CODE_TO_EDGE_PUSH =
{
[Enum.KeyCode.DPadDown] = Vector2.new(0.0, DPAD_STEP_AMOUNT);
[Enum.KeyCode.DPadUp] = Vector2.new(0.0, -DPAD_STEP_AMOUNT);
[Enum.KeyCode.DPadLeft] = Vector2.new(-DPAD_STEP_AMOUNT, 0.0);
[Enum.KeyCode.DPadRight] = Vector2.new(DPAD_STEP_AMOUNT, 0.0);
}
local function CreateOverscanAdjustmentScreen(parent)
local this = {}
this.StickPosition = ZERO_VEC2
this.StickAcceleration = 1
local lastUpdate = nil
local lastParent = parent
local lastSavedOverscanPX = START_EDGE_SIZE.X.Scale
local lastSavedOverscanPY = START_EDGE_SIZE.Y.Scale
local function StorePreviousOverscanValues()
local success, errormsg = pcall(function()
lastSavedOverscanPX = GameOptionsSettings.OverscanPX
lastSavedOverscanPY = GameOptionsSettings.OverscanPY
end)
if not success then
print("Error, StorePreviousOverscanValues: OverscanPX and OverscanPY" , errormsg)
end
end
StorePreviousOverscanValues()
local MainContainer = Utility.Create'Frame'
{
Name = 'OverscanAdjustmentScreen';
Size = UDim2.new(1, 0, 1, 0);
BorderSizePixel = 1;
BackgroundTransparency = 0;
BackgroundColor3 = BACKGROUND_COLOR;
Visible = false;
}
local BackgroundImage = Utility.Create'ImageLabel'
{
Name = 'BackgroundImage';
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Background.png';
Size = UDim2.new(1,0,1,0);
BackgroundTransparency = 1;
Parent = MainContainer;
};
local Title = Utility.Create'TextLabel'
{
Name = 'Title';
Text = Strings:LocalizedString('ScreenSizeWord');
Position = UDim2.new(0, 230, 0, 175);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local titleTextSize = TextService:GetTextSize(Title.Text, Utility.ConvertFontSizeEnumToInt(Title.FontSize), Title.Font, Vector2.new())
Title.Size = UDim2.new(0, titleTextSize.X, 0, titleTextSize.Y)
end
local Prompt = Utility.Create'TextLabel'
{
Name = 'Prompt';
Text = Strings:LocalizedString('ResizeScreenPrompt');
Position = UDim2.new(0,230,0,225);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local ControllerImage = Utility.Create'ImageLabel'
{
Name = 'ControllerImage';
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Controller@1080.png';
Size = UDim2.new(0,599,0,404);
Parent = MainContainer;
};
Utility.CalculateAnchor(ControllerImage, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local Line = Utility.Create'Frame'
{
Name = 'Line';
Size = UDim2.new(0.4,0,0,1);
Position = UDim2.new(0.73, 0, 0.545, 0);
BackgroundColor3 = Color3.new(1,1,1);
BorderSizePixel = 0;
BackgroundTransparency = 0;
Parent = ControllerImage;
};
local InputHint = Utility.Create'TextLabel'
{
Name = 'InputHint';
Text = Strings:LocalizedString('ResizeScreenInputHint');
Size = UDim2.new(0,0,0,0);
Position = UDim2.new(1, 3, 0, -1);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = Line;
};
end
local AcceptButtonImage = Utility.Create'ImageLabel'
{
Name = 'AcceptButtonImage';
Size = UDim2.new(0,65,0,65);
Position = UDim2.new(0.5, 25, 0.75, 0);
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/AButton.png';
BackgroundTransparency = 1;
Parent = MainContainer;
};
local AcceptHint = Utility.Create'TextLabel'
{
Name = 'AcceptHint';
Text = Strings:LocalizedString('AcceptWord'):upper();
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1, 5, 0, -3);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = AcceptButtonImage;
};
local ResetButtonImage = Utility.Create'ImageLabel'
{
Name = 'ResetButtonImage';
Size = UDim2.new(0,65,0,65);
Image = 'rbxasset://textures/ui/Shell/ButtonIcons/XButton.png';
BackgroundTransparency = 1;
Parent = MainContainer;
};
local ResetHint = Utility.Create'TextLabel'
{
Name = 'ResetHint';
Text = Strings:LocalizedString('ResetWord'):upper();
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1, 5, 0, -3);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
BackgroundTransparency = 1;
Parent = ResetButtonImage;
};
local Edges = Utility.Create'Frame'
{
Name = 'Edges';
Size = START_EDGE_SIZE;
BackgroundTransparency = 1;
Parent = MainContainer;
};
do
local edgesSelectionImage = Utility.Create'ImageLabel'
{
Name = 'EdgesSelectionImage';
Size = UDim2.new(1,2,1,2);
Position = UDim2.new(0,-1,0,-1);
Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenRangeOverlay.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(21,21,41,41);
BackgroundTransparency = 1;
};
edgesSelectionImage.Parent = Edges
end
for i = 0, 3 do
local CornerImage = Utility.Create'ImageLabel'
{
Name = 'CornerImage';
Size = UDim2.new(0, 95, 0, 95);
BackgroundTransparency = 1;
Rotation = 90 * i;
Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenAdjustmentArrow.png";
Parent = Edges;
};
if i == 0 then
Utility.CalculateAnchor(CornerImage, UDim2.new(0,0,0,0), Utility.Enum.Anchor.TopLeft)
elseif i == 1 then
Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 0, 0), Utility.Enum.Anchor.TopRight)
elseif i == 2 then
Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 1, 0), Utility.Enum.Anchor.BottomRight)
elseif i == 3 then
Utility.CalculateAnchor(CornerImage, UDim2.new(0, 0, 1, 0), Utility.Enum.Anchor.BottomLeft)
end
end
local function RefreshLayout()
ResetButtonImage.Position = UDim2.new(0.5 - ResetButtonImage.Size.X.Scale, -ResetHint.TextBounds.X - 25 - ResetButtonImage.Size.X.Offset, 0.75, 0);
Prompt.Size = UDim2.new(0, Prompt.TextBounds.X, 0, Prompt.TextBounds.Y)
end
local closedEvent = Instance.new("BindableEvent")
closedEvent.Name = "ClosedEvent"
this.Closed = closedEvent.Event
local EdgePercent = Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y)
function this:GetAdjustmentEdgesPercent()
return EdgePercent
end
function this:SetAdjustmentEdges(newEdgeSize)
EdgePercent = Utility.ClampVector2(Vector2.new(MIN_EDGE_SIZE.X.Scale, MIN_EDGE_SIZE.Y.Scale), Vector2.new(1,1), newEdgeSize)
local guiSize = GuiRoot.AbsoluteSize
local absoluteEdgeSize = EdgePercent * guiSize
-- Round to nearest 2 so that we can evenly space above and below
local roundedAbsoluteEdgeSize = Vector2.new(Utility.Round(absoluteEdgeSize.X/2), Utility.Round(absoluteEdgeSize.Y/2)) * 2
roundedAbsoluteEdgeSize = Utility.ClampVector2(Vector2.new(), guiSize, roundedAbsoluteEdgeSize)
Edges.Size = UDim2.new(0, roundedAbsoluteEdgeSize.X, 0, roundedAbsoluteEdgeSize.Y)
Edges.Position = UDim2.new(0, (guiSize.X - roundedAbsoluteEdgeSize.X) / 2, 0, (guiSize.Y - roundedAbsoluteEdgeSize.Y) / 2)
end
function this:PushAdjustmentEdges(pushAmount)
self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount)
end
function this:PushAdjustmentEdgesByPixels(pixelPushAmount)
local guiSize = GuiRoot.AbsoluteSize
local pushAmount = pixelPushAmount / guiSize
if Utility.IsFinite(pushAmount.X) and Utility.IsFinite(pushAmount.Y) then
self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount)
end
end
function this:Update()
local now = tick()
if lastUpdate and self.StickPosition ~= ZERO_VEC2 then
local delta = now - lastUpdate
local transformedStick = Utility.GamepadLinearToCurve(self.StickPosition, 0.2)
self:PushAdjustmentEdges(Vector2.new(transformedStick.X, -transformedStick.Y) * self.StickAcceleration * delta * THUMBSTICK_BORDER_SENSITIVITY)
if transformedStick ~= ZERO_VEC2 then
self.StickAcceleration = math.min(self.StickAcceleration + delta * ACCELERATION_RATE, MAX_STICK_ACCELERATION)
else
self.StickAcceleration = 1
end
end
lastUpdate = now
end
function this:ResetEdges()
self:SetAdjustmentEdges(Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y))
end
local GuiRootChangedConn = nil
function this:Show()
MainContainer.Visible = true
MainContainer.Parent = lastParent
RefreshLayout()
self.StickAcceleration = 1
if not UserSettings().GameSettings:InStudioMode() and GameOptionsSettings.OverscanPX > 0 and GameOptionsSettings.OverscanPY > 0 then
this:SetAdjustmentEdges(Vector2.new(GameOptionsSettings.OverscanPX, GameOptionsSettings.OverscanPY))
else
self:ResetEdges()
end
local success, errormsg = pcall(function()
StorePreviousOverscanValues()
-- Need to do this step so that the player
-- can accurately estimate their TV's overscan
GameOptionsSettings.OverscanPX = 1
GameOptionsSettings.OverscanPY = 1
end)
if not success then
print("Error resetting Overscan Screen Resolution:" , errormsg)
end
Utility.DisconnectEvent(GuiRootChangedConn)
GuiRootChangedConn = GuiRoot.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
self:PushAdjustmentEdgesByPixels(ZERO_VEC2)
end
end)
self:PushAdjustmentEdgesByPixels(ZERO_VEC2)
end
function this:Hide()
MainContainer.Visible = false
MainContainer.Parent = nil
GuiRootChangedConn = Utility.DisconnectEvent(GuiRootChangedConn)
end
function this:Focus()
GuiService.SelectedCoreObject = nil
RefreshLayout()
self.StickPosition = ZERO_VEC2
self.StickAcceleration = 1
local beginSeen = false
ContextActionService:BindCoreAction("ResetAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
self:ResetEdges()
end
end,
false,
Enum.KeyCode.ButtonX)
ContextActionService:BindCoreAction("AcceptAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
beginSeen = true
elseif inputState == Enum.UserInputState.End and beginSeen then
local success, errormsg = pcall(function()
GameOptionsSettings.OverscanPX = math.min(1, self:GetAdjustmentEdgesPercent().X)
GameOptionsSettings.OverscanPY = math.min(1, self:GetAdjustmentEdgesPercent().Y)
StorePreviousOverscanValues()
end)
if not success then
print("Error setting Overscan Screen Resolution:" , errormsg)
end
if self == ScreenManager:GetTopScreen() then
ScreenManager:CloseCurrent()
closedEvent:Fire()
end
end
end,
false,
Enum.KeyCode.ButtonA)
ContextActionService:BindCoreAction("ThumbstickAdjustmentScreen",
function(actionName, inputState, inputObject)
self.StickPosition = Vector2.new(inputObject.Position.X, inputObject.Position.Y)
end,
false,
Enum.KeyCode.Thumbstick2)
ContextActionService:BindCoreAction("DPadAdjustmentScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
local pushAmount = DPAD_CODE_TO_EDGE_PUSH[inputObject.KeyCode]
if pushAmount then
self:PushAdjustmentEdgesByPixels(pushAmount)
end
end
end,
false,
Enum.KeyCode.DPadDown, Enum.KeyCode.DPadUp, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight)
RunService:BindToRenderStep("UpdateAdjustmentScreen", Enum.RenderPriority.Input.Value, function() self:Update() end)
end
function this:RemoveFocus()
self.StickPosition = ZERO_VEC2
lastUpdate = nil
ContextActionService:UnbindCoreAction("ResetAdjustmentScreen")
ContextActionService:UnbindCoreAction("AcceptAdjustmentScreen")
ContextActionService:UnbindCoreAction("ThumbstickAdjustmentScreen")
ContextActionService:UnbindCoreAction("DPadAdjustmentScreen")
RunService:UnbindFromRenderStep("UpdateAdjustmentScreen")
end
function this:SetPosition(newPosition)
MainContainer.Position = newPosition
end
function this:SetParent(newParent)
MainContainer.Parent = newParent
lastParent = newParent
end
function this:SetStyleForInGame()
BackgroundImage.Visible = false
MainContainer.BackgroundTransparency = 0.2
local function setZIndex(guiObject, newZIndex)
if not guiObject:IsA("GuiObject") then return end
guiObject.ZIndex = newZIndex
local children = guiObject:GetChildren()
for i = 1, #children do
setZIndex(children[i], newZIndex)
end
end
setZIndex(MainContainer, 9)
end
if not UserSettings().GameSettings:InStudioMode() then
pcall(function() PlatformService.Suspended:connect(function()
GameOptionsSettings.OverscanPX = lastSavedOverscanPX
GameOptionsSettings.OverscanPY = lastSavedOverscanPY
end)
end)
end
return this
end
return CreateOverscanAdjustmentScreen
@@ -0,0 +1,380 @@
--[[
// PackageData.lua
// Created by Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local Http = require(Modules:FindFirstChild('Http'))
local UserData = require(Modules:FindFirstChild('UserData'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ContentProvider = game:GetService("ContentProvider")
local MarketplaceService = Game:GetService('MarketplaceService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local CurrentlyWearingAssetId = nil
local RequestingWearAsset = false
local function AwaitWearAssetRequest()
while RequestingWearAsset do wait(0.1) end
end
-- local function PreloadCharacterAppearanceAsync()
-- -- Preload content so that when we join a game our appearance is cached
-- local myAsset = Http.GetCharactersAssetsAsync(UserData:GetLocalUserIdAsync())
-- print("MyAsset:" , myAsset)
-- if myAsset then
-- local assetList = Utility.SplitString(myAsset, ";")
-- print("Parsed:" , Utility.PrettyPrint(assetList))
-- ContentProvider:PreloadAsync(assetList)
-- end
-- end
local function PreloadCharacterAppearanceAsync()
local character = nil
local success, msg = pcall(function()
character = game.Players:GetCharacterAppearanceAsync(UserData:GetLocalUserIdAsync())
end)
if character then
local assetUrl = Http.BaseUrl .. 'asset/?id='
local assetList = Utility.FindAssetsInModel(character, assetUrl)
-- print("Preloading:" , Utility.PrettyPrint(assetList))
ContentProvider:PreloadAsync(assetList)
end
return success
end
local function SetOwnedInternal(packageData, newValue)
if packageData.Owned ~= newValue then
packageData.Owned = newValue
packageData.OwnershipChanged:fire(packageData.Owned)
end
end
local function CreatePackageItem(data)
local this = {}
this.Owned = false
this.OwnershipChanged = Utility.Signal()
this.IsWearingChanged = Utility.Signal()
local productInfo = nil
-- print("PackageData:" , Utility.PrettyPrint(data))
function this:GetAssetId()
local assetId = data and data['AssetId']
if not assetId then
assetId = data and data['Item'] and data['Item']['AssetId']
end
return assetId
end
function this:GetProductIdAsync()
while not productInfo do wait() end
return productInfo and productInfo['ProductId']
end
function this:BuyAsync()
print("Do buy" , 'productId' , self:GetProductIdAsync() , 'robuxPrice' , self:GetRobuxPrice())
local purchaseResult = Http.PurchaseProductAsync(self:GetProductIdAsync(), self:GetRobuxPrice(), self:GetCreatorId(), 1)
local nowOwns = purchaseResult and purchaseResult['TransactionVerb'] == 'bought'
if nowOwns then
SetOwnedInternal(self, nowOwns)
end
return purchaseResult
end
function this:IsOwned()
return self.Owned
end
local lastIsWearing = nil
function this:IsWearing()
local wasWearing = lastIsWearing
lastIsWearing = CurrentlyWearingAssetId and CurrentlyWearingAssetId == self:GetAssetId() and self:IsOwned()
if lastIsWearing ~= wasWearing then
self.IsWearingChanged:fire(lastIsWearing)
end
return lastIsWearing
end
function this:GetRobuxPrice()
local robuxPrice = data and data['PriceInRobux']
if not robuxPrice then
robuxPrice = data and data['Product'] and data['Product']['PriceInRobux']
end
local isPublicDomain = data and data['IsPublicDomain'] == true
if isPublicDomain == nil then
isPublicDomain = data and data['Product'] and data['Product']['IsPublicDomain'] == true
end
if not robuxPrice and isPublicDomain then
robuxPrice = 0
end
return robuxPrice
end
function this:GetCreatorId()
return data and data['Creator'] and data['Creator']['Id']
end
function this:WearAsync()
local assetId = self:GetAssetId()
if assetId then
AwaitWearAssetRequest()
EventHub:dispatchEvent(EventHub.Notifications["AvatarEquipBegin"], assetId)
RequestingWearAsset = true
local result = Http.PostWearAssetAsync(assetId)
RequestingWearAsset = false
if result and result['success'] == true then
EventHub:dispatchEvent(EventHub.Notifications["DonnedDifferentPackage"], assetId)
end
-- print("Http result from post wear asset:")
-- print(Utility.PrettyPrint(result))
return result
end
end
function this:GetName()
local resultPackageName = self:GetFullName()
if resultPackageName then
local colonPosition = string.find(resultPackageName, ":")
if colonPosition then
resultPackageName = string.sub(resultPackageName, 1, colonPosition - 1)
end
else
resultPackageName = "Unknown"
end
return resultPackageName
end
function this:GetFullName()
local name = data and data['Name']
if not name then
name = data and data['Item'] and data["Item"]['Name']
end
return name or "Unknown"
end
function this:GetDescriptionAsync()
while not productInfo do wait() end
return productInfo and productInfo['Description']
end
spawn(function()
productInfo = MarketplaceService:GetProductInfo(this:GetAssetId())
if productInfo == nil then productInfo = {} end
end)
return this
end
local PackageData = {}
local PackageCache = nil
local function GetAvailableXboxCatalogPackagesAsync()
local isFinalPage = false
local packages = {}
local index = 0
local count = 20
repeat
local result = nil
Utility.ExponentialRepeat(
function() return result == nil end,
function() result = Http.GetXboxProductsAsync(index, count) end,
2)
if result then
local items = result['Products']
if items then
if #items < count then
isFinalPage = true
end
for _, itemInfo in pairs(items) do
table.insert(packages, CreatePackageItem(itemInfo))
end
end
end
index = index + count
until result == nil or isFinalPage
-- print("Xbox products:" , packages , Utility.PrettyPrint(packages))
if isFinalPage then
return packages
end
end
local function GetOwnedCatalogPackageIdsByUserAsync(userId)
local packages = Http.GetUserOwnedPackagesAsync(userId)
if packages then
local data = packages['IsValid'] and packages['Data']
local items = data and data['Items']
local result = {}
-- print("Items" , Utility.PrettyPrint(items))
if items then
for _, itemInfo in pairs(items) do
local assetId = itemInfo and itemInfo['Item'] and itemInfo['Item']['AssetId']
result[assetId] = itemInfo
end
end
return result
end
end
local function getCatalogPackagesAsync()
local xboxCatalogPackages = GetAvailableXboxCatalogPackagesAsync()
local myPackages = GetOwnedCatalogPackageIdsByUserAsync(UserData:GetRbxUserId())
if xboxCatalogPackages and myPackages then
local result = {}
for _, xboxPackage in pairs(xboxCatalogPackages) do
local owned = (myPackages[xboxPackage:GetAssetId()] ~= nil)
SetOwnedInternal(xboxPackage, owned)
table.insert(result, xboxPackage)
end
-- NOTE: Temporary changed to get items that you bought outside xbox
-- Create a map of what we already have
local haveAssets = {}
for _, package in pairs(result) do
haveAssets[package:GetAssetId()] = true
end
for assetId, packageInfo in pairs(myPackages) do
if not haveAssets[assetId] then
local myPackage = CreatePackageItem(packageInfo)
SetOwnedInternal(myPackage, true)
table.insert(result, myPackage)
end
end
return result
end
end
local function SetCurrentlyWearingAssetId(newValue)
if CurrentlyWearingAssetId ~= newValue then
CurrentlyWearingAssetId = newValue
if PackageCache then
for _, package in pairs(PackageCache) do
package:IsWearing()
end
end
end
end
local UserChangedCount = 0
local function OnUserAccountChanged()
UserChangedCount = UserChangedCount + 1
PackageCache = nil
SetCurrentlyWearingAssetId(nil)
local wearingAsset = PackageData:GetCurrentlyWearingPackageAssetIdAsync()
if not CurrentlyWearingAssetId then
SetCurrentlyWearingAssetId(wearingAsset)
end
end
spawn(function()
local function queryWearingAsset()
local wearingAsset = PackageData:GetCurrentlyWearingPackageAssetIdAsync()
SetCurrentlyWearingAssetId(wearingAsset)
end
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentPackage"], "PackageData",
function(assetId)
SetCurrentlyWearingAssetId(assetId)
spawn(PreloadCharacterAppearanceAsync)
end)
EventHub:addEventListener(EventHub.Notifications["DonnedDifferentOutfit"], "PackageData",
function(outfitId)
queryWearingAsset()
spawn(PreloadCharacterAppearanceAsync)
end)
EventHub:addEventListener(EventHub.Notifications["AuthenticationSuccess"], "PackageData", OnUserAccountChanged)
-- Wait until we are ready to make the call be being signed in
if UserData:GetRbxUserId() then
queryWearingAsset()
spawn(PreloadCharacterAppearanceAsync)
end
end)
local debounceGetXboxCatalogPackages = false
function PackageData:GetXboxCatalogPackagesAsync()
if debounceGetXboxCatalogPackages then
while debounceGetXboxCatalogPackages do wait() end
end
debounceGetXboxCatalogPackages = true
-- Ensure that the catalog data we are getting is applicable to the
-- currently logged in user
-- while not PackageCache do
local startCount = UserChangedCount
local packageData = nil
Utility.ExponentialRepeat(
function() return packageData == nil and startCount == UserChangedCount end,
function() packageData = getCatalogPackagesAsync() end,
3)
if startCount == UserChangedCount then
PackageCache = packageData
end
-- end
debounceGetXboxCatalogPackages = false
return PackageCache
end
function PackageData:GetCurrentlyWearingPackageAssetIdAsync()
local currentWearingData = Http.GetXboxCurrentlyWearingPackageAsync()
return currentWearingData and currentWearingData['AssetId']
end
function PackageData:GetCachedWearingPackage()
return CurrentlyWearingAssetId
end
function PackageData:AwaitWearAssetRequest()
AwaitWearAssetRequest()
end
return PackageData
@@ -0,0 +1,65 @@
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService;
pcall(function() PlatformService = Game:GetService('PlatformService') end)
local PlatformCatalogData = {}
local function getStudioDummyData()
return {{ReducedName = 'Default Short Title', Description = 'Default Description', DisplayListPrice = '$199.99', IsPartOfAnyBundle = false, DisplayPrice = '$0.80', ProductId = '210d1d69-5189-40f4-a59b-ecfb4f849847', Name = '22,500 ROBUX', TitleId = 0, IsBundle = false}, {ReducedName = 'Default Short Title', Description = 'Default Description', DisplayListPrice = '$3.00', IsPartOfAnyBundle = false, DisplayPrice = '$3.00', ProductId = '70c2075d-5e2f-4ffd-8de5-8a6d2f5e65ad', Name = '400 ROBUX', TitleId = 0, IsBundle = false}, {ReducedName = 'Default Short Title', Description = 'Default Description', DisplayListPrice = '$2.20', IsPartOfAnyBundle = false, DisplayPrice = '$2.20', ProductId = '878c642b-cb27-4d5e-a150-a408ea40c41c', Name = '240 ROBUX', TitleId = 0, IsBundle = false}}
end
function PlatformCatalogData:GetCatalogInfoAsync()
if UserSettings().GameSettings:InStudioMode() then
return getStudioDummyData(),
true,
''
end
local numRetries = 5
local catalogInfo, success, errormsg;
for i = 1, numRetries do
success, errormsg = pcall(function()
catalogInfo = PlatformService:BeginGetCatalogInfo()
end)
if success and catalogInfo then
return catalogInfo, success, errormsg
end
wait(10)
end
return catalogInfo, success, errormsg
end
function PlatformCatalogData:ParseDisplayPrice(productInfo)
local rawText = productInfo and productInfo.DisplayListPrice
local noCurrency = rawText and string.gsub(rawText, "%$", "") or nil
noCurrency = noCurrency and string.gsub(noCurrency, ",", "") or nil
return noCurrency and tonumber(noCurrency) or 0.99
end
function PlatformCatalogData:ParseRobuxValue(productInfo)
local rawText = productInfo and productInfo.Name
local noJunk = string.gsub(rawText, ",", "")
noJunk = noJunk and string.match(noJunk, "[0-9]+") or nil
return noJunk and tonumber(noJunk) or 1000
end
function PlatformCatalogData:CalculateRobuxRatio(productInfo)
local robuxValue = self:ParseRobuxValue(productInfo)
local displayPrice = self:ParseDisplayPrice(productInfo)
if displayPrice == 0 or robuxValue == 0 then
return 0
end
return robuxValue / displayPrice
end
return PlatformCatalogData
@@ -0,0 +1,38 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
-- Platform Interface
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local PlatformInterface = {}
local gettingFriends = false
function PlatformInterface:GetPartyMembersAsync()
while gettingFriends do wait() end
gettingFriends = true
local partyMembers;
local success, msg = pcall(function()
partyMembers = PlatformService:GetPlatformPartyMembers()
end)
if not success then
print("HeroStatsManager - Error getting party members:" , msg)
end
gettingFriends = false
return partyMembers
end
function PlatformInterface:IsInAParty(partyMembers)
return (partyMembers and #partyMembers > 1)
end
return PlatformInterface
@@ -0,0 +1,123 @@
--[[
// PopupText.lua
// Creates a transparent text label that pops up when
// its parent it selected
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local TextService = game:GetService('TextService')
local VERTICAL_PADDING = 12
local HORIZONTAL_PADDING = 18
local SELECTION_BORDER = 7
local createPopupText = function(parent, text)
local this = {}
local tweenTime = 0.3
local easingStyle = Enum.EasingStyle.Quad
local easingDirection = Enum.EasingDirection.Out
local currentZIndex = 2
local clipFrame = Utility.Create'Frame'
{
Name = "ClipFrame";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
ZIndex = currentZIndex;
ClipsDescendants = true;
Parent = parent
}
local bg = Utility.Create'Frame'
{
Name = "PopupBG";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0, 0, 1, 5);
BackgroundTransparency = GlobalSettings.ModalBackgroundTransparency;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = currentZIndex;
Parent = clipFrame;
}
local nameLabel = Utility.Create'TextLabel'
{
Name = "NameLabel";
Size = UDim2.new(1, -HORIZONTAL_PADDING, 1, -VERTICAL_PADDING);
Position = UDim2.new(0, HORIZONTAL_PADDING/2 + SELECTION_BORDER, 0, VERTICAL_PADDING/2);
BackgroundTransparency = 1;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
ZIndex = currentZIndex;
Text = text;
Parent = bg;
}
-- resize based on text bounds
local function resizeBounds()
local nameLabelTextSize = TextService:GetTextSize(
nameLabel.Text,
Utility.ConvertFontSizeEnumToInt(nameLabel.FontSize),
nameLabel.Font,
Vector2.new(clipFrame.AbsoluteSize.x - SELECTION_BORDER - HORIZONTAL_PADDING, clipFrame.AbsoluteSize.y - SELECTION_BORDER -VERTICAL_PADDING))
local newSizeX = nameLabelTextSize.x + HORIZONTAL_PADDING
local newSizeY = math.min(nameLabelTextSize.y + VERTICAL_PADDING, parent.AbsoluteSize.y * 0.75)
bg.Size = UDim2.new(0, newSizeX + SELECTION_BORDER, 0, newSizeY + SELECTION_BORDER)
end
spawn(function()
resizeBounds()
end)
parent.SelectionGained:connect(function()
if #nameLabel.Text > 0 then
resizeBounds()
Utility.TweenPositionOrSet(bg, UDim2.new(0, 0, 1, -bg.Size.Y.Offset), easingDirection, easingStyle, tweenTime, true)
end
end)
parent.SelectionLost:connect(function()
Utility.TweenPositionOrSet(bg, UDim2.new(0, 0, 1, 5), easingDirection, easingStyle, tweenTime, true)
end)
function this:SetTweenTime(value)
tweenTime = value
end
function this:SetEasingStyle(style)
easingStyle = style
end
function this:SetEasingDirection(direction)
easingDirection = direction
end
function this:SetText(text)
nameLabel.Text = text
resizeBounds()
if #text == 0 then
bg.Position = UDim2.new(0, 0, 1, 5)
end
end
function this:SetZIndex(zindex)
if zindex ~= currentZIndex then
currentZIndex = zindex
clipFrame.ZIndex = currentZIndex
bg.ZIndex = currentZIndex
nameLabel.ZIndex = currentZIndex
end
end
return this
end
return createPopupText
@@ -0,0 +1,501 @@
--[[
// PurchasePackagePrompt.lua
// Kip Turner
// Copyright Roblox 2015
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local MarketplaceService = game:GetService('MarketplaceService')
local PlatformService;
pcall(function() PlatformService = game:GetService('PlatformService') end)
local UserDataModule = require(Modules:FindFirstChild('UserData'))
local Http = require(Modules:FindFirstChild('Http'))
local ScrollingTextBox = require(Modules:FindFirstChild('ScrollingTextBox'))
local CreateConfirmPrompt = require(Modules:FindFirstChild('ConfirmPrompt'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local Errors = require(Modules:FindFirstChild('Errors'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local CurrencyWidgetModule = require(Modules:FindFirstChild('CurrencyWidget'))
local MOCKUP_WIDTH = 1920
local MOCKUP_HEIGHT = 1080
local CONTENT_WIDTH = 1920
local CONTENT_HEIGHT = 690
local PACKAGE_CONTAINER_WIDTH = 780
local PACKAGE_CONTAINER_HEIGHT = 690
local PACKAGE_BACKGROUND_WIDTH = 580
local PACKAGE_BACKGROUND_HEIGHT = 640
local CONTENT_POSITION = Vector2.new(0, 225)
local DETAILS_CONTAINER_WIDTH = CONTENT_WIDTH - PACKAGE_CONTAINER_WIDTH
local DETAILS_CONTAINER_HEIGHT = 690
local DESCRIPTION_WIDTH = 800
local DESCRIPTION_HEIGHT = 320
local BUY_BUTTON_WIDTH = 320
local BUY_BUTTON_HEIGHT = 64
local TEXT_START_OFFSET = Vector2.new(0, 70)
local TEXT_SPACING = Vector2.new(0, 20)
local ROBUX_TEXT_OFFSET = Vector2.new(0, 25)
local DETAIL_TEXT_OFFSET = Vector2.new(0, 30)
local BUY_BUTTON_OFFSET = Vector2.new(0, -50)
local ROBUX_BALANCE_OFFSET = Vector2.new(100, -130)
local function CreatePurchasePackagePrompt(packageInfo)
local this = {}
local MyParent = nil
local Result = nil
local purchasing = false
local finishedLoading = false
local balance = nil
local inFocus = false
local ResultEvent = Utility.Signal()
local packageName = packageInfo:GetFullName()
local robuxPrice = packageInfo:GetRobuxPrice()
local creatorId = packageInfo:GetCreatorId()
local ModalBackground = Utility.Create'Frame'
{
Name = "PurchasePackagePrompt";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local ContentContainer = Utility.Create'Frame'
{
Name = "ContentContainer";
Size = UDim2.new(CONTENT_WIDTH/MOCKUP_WIDTH, 0, CONTENT_HEIGHT/MOCKUP_HEIGHT, 0);
Position = UDim2.new(CONTENT_POSITION.x/MOCKUP_WIDTH, 0, CONTENT_POSITION.y/MOCKUP_HEIGHT, 0);
BackgroundTransparency = 0;
BackgroundColor3 = GlobalSettings.OverlayColor;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ModalBackground;
}
-- Utility.CalculateAnchor(ContentContainer, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local PackageContainer = Utility.Create'Frame'
{
Name = "PackageContainer";
Size = UDim2.new(PACKAGE_CONTAINER_WIDTH/CONTENT_WIDTH, 0, PACKAGE_CONTAINER_HEIGHT/CONTENT_HEIGHT, 0);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ContentContainer;
}
local PackageBackground = Utility.Create'Frame'
{
Name = "PackageBackground";
Size = UDim2.new(PACKAGE_BACKGROUND_WIDTH/PACKAGE_CONTAINER_WIDTH, 0, PACKAGE_BACKGROUND_HEIGHT/CONTENT_HEIGHT, 0);
BackgroundTransparency = 0;
BackgroundColor3 = GlobalSettings.ForegroundGreyColor;
BorderSizePixel = 0;
ClipsDescendants = true;
ZIndex = 4;
Parent = PackageContainer;
}
Utility.CalculateAnchor(PackageBackground, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local PackageImage = Utility.Create'ImageLabel'
{
Name = 'PackageImage';
Size = UDim2.new(1,0,1,0);
Image = Http.GetThumbnailUrlForAsset(packageInfo:GetAssetId());
BackgroundTransparency = 1;
ZIndex = 4;
Parent = PackageBackground;
};
local DetailsContainer = Utility.Create'Frame'
{
Name = "DetailsContainer";
Size = UDim2.new(DETAILS_CONTAINER_WIDTH/CONTENT_WIDTH, 0, DETAILS_CONTAINER_HEIGHT/CONTENT_HEIGHT, 0);
Position = UDim2.new(PACKAGE_CONTAINER_WIDTH/CONTENT_WIDTH,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 4;
Parent = ContentContainer;
}
local PurchasingTitle = Utility.Create'TextLabel'
{
Name = 'PurchasingTitle';
Text = Strings:LocalizedString('PurchasingTitle');
Position = UDim2.new(0, 0, 0, 66);
Size = UDim2.new(1,0,0,25);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.HeadingFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = 4;
Visible = false;
Parent = DetailsContainer;
};
local DetailsContent = Utility.Create'Frame'
{
Name = "DetailsContent";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0,0,0,0);
BackgroundTransparency = 1;
BorderSizePixel = 0;
ZIndex = 4;
Parent = DetailsContainer;
}
local PackageName = Utility.Create'TextLabel'
{
Name = 'PackageName';
Text = packageName or "Unknown Package";
-- Position = UDim2.new(TEXT_START_OFFSET.X/DETAILS_CONTAINER_WIDTH, 0, TEXT_START_OFFSET.Y/DETAILS_CONTAINER_HEIGHT, 0);
Position = UDim2.new(0, 0, 0, 66);
Size = UDim2.new(1,0,0,25);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.HeadingFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = DetailsContent;
};
local RobuxIcon = Utility.Create'ImageLabel'
{
Name = 'RobuxIcon';
Position = UDim2.new(0,0,0,125);
-- Position = PackageName.Position + UDim2.new(ROBUX_TEXT_OFFSET.X/DETAILS_CONTAINER_WIDTH,0,ROBUX_TEXT_OFFSET.Y/DETAILS_CONTAINER_HEIGHT + PackageName.Size.Y.Scale, PackageName.Size.Y.Offset);
Size = UDim2.new(0,50,0,50);
BackgroundTransparency = 1;
ZIndex = 4;
Parent = DetailsContent;
};
AssetManager.LocalImage(RobuxIcon, 'rbxasset://textures/ui/Shell/Icons/ROBUXIcon', {['720'] = UDim2.new(0,28,0,28); ['1080'] = UDim2.new(0,42,0,42);})
local PackageCost = Utility.Create'TextLabel'
{
Name = 'PackageCost';
Text = '';
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1.3,0,0,0);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.GreenTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = RobuxIcon;
};
local AlreadyOwnTextLabel = Utility.Create'TextLabel'
{
Name = 'AlreadyOwnTextLabel';
Text = '';
Size = UDim2.new(0,0,0,50);
Position = UDim2.new(0,0,0,125);
-- Position = PackageName.Position + UDim2.new(ROBUX_TEXT_OFFSET.X/DETAILS_CONTAINER_WIDTH,0,ROBUX_TEXT_OFFSET.Y/DETAILS_CONTAINER_HEIGHT + PackageName.Size.Y.Scale, PackageName.Size.Y.Offset);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.GreenTextColor;
Font = GlobalSettings.ItalicFont;
FontSize = GlobalSettings.DescriptionSize;
BackgroundTransparency = 1;
ZIndex = 4;
Visible = false;
Parent = DetailsContent;
};
local descriptionScrollingTextBox = ScrollingTextBox(UDim2.new(DESCRIPTION_WIDTH/DETAILS_CONTAINER_WIDTH, 0, DESCRIPTION_HEIGHT/DETAILS_CONTAINER_HEIGHT, 0),
-- RobuxIcon.Position + UDim2.new(DETAIL_TEXT_OFFSET.X/DETAILS_CONTAINER_WIDTH,0,DETAIL_TEXT_OFFSET.Y/DETAILS_CONTAINER_HEIGHT + RobuxIcon.Size.Y.Scale, RobuxIcon.Size.Y.Offset),
UDim2.new(0, 0, 0, 200),
DetailsContent)
descriptionScrollingTextBox:SetZIndex(5)
descriptionScrollingTextBox:SetFontSize(GlobalSettings.TitleSize)
descriptionScrollingTextBox:SetFont(GlobalSettings.LightFont)
local BuyButton = Utility.Create'ImageButton'
{
Name = "BuyButton";
Size = UDim2.new(BUY_BUTTON_WIDTH/DETAILS_CONTAINER_WIDTH, 0, BUY_BUTTON_HEIGHT/DETAILS_CONTAINER_HEIGHT, 0);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
BackgroundTransparency = 0;
ZIndex = 4;
Parent = DetailsContent;
}
Utility.CalculateAnchor(BuyButton, UDim2.new(0, 0, 1 + BUY_BUTTON_OFFSET.Y/DETAILS_CONTAINER_HEIGHT, 0), Utility.Enum.Anchor.BottomLeft)
local BuyText = Utility.Create'TextLabel'
{
Name = 'BuyText';
Text = '';
Size = UDim2.new(1,0,1,0);
TextColor3 = GlobalSettings.TextSelectedColor;
Font = GlobalSettings.HeadingFont;
FontSize = GlobalSettings.MediumLargeHeadingSize;
BackgroundTransparency = 1;
ZIndex = 4;
Parent = BuyButton;
};
local function OnOwnedUpdate()
if packageInfo:IsOwned() or robuxPrice == nil then
if packageInfo:IsOwned() then
if robuxPrice and robuxPrice > 0 then
AlreadyOwnTextLabel.Text = string.format(Strings:LocalizedString('PurchasedThisPhrase'), Utility.FormatNumberString(tostring(robuxPrice)))
else
AlreadyOwnTextLabel.Text = Strings:LocalizedString('AlreadyOwnedPhrase')
end
end
AlreadyOwnTextLabel.Visible = (packageInfo:IsOwned() == true)
RobuxIcon.Visible = false
BuyText.Text = Strings:LocalizedString("OkWord")
else
if robuxPrice and robuxPrice == 0 then
PackageCost.Text = Strings:LocalizedString('FreeWord'):upper();
else
PackageCost.Text = robuxPrice and Utility.FormatNumberString(tostring(robuxPrice)) or '-';
end
RobuxIcon.Visible = true
AlreadyOwnTextLabel.Visible = false
if robuxPrice == 0 then
BuyText.Text = Strings:LocalizedString('TakeWord'):upper();
elseif balance and robuxPrice > balance then
BuyText.Text = Strings:LocalizedString('GetRobuxPhrase'):upper();
else
BuyText.Text = Strings:LocalizedString('BuyWord'):upper();
end
end
end
local function SetBalance(newBalance)
balance = newBalance
OnOwnedUpdate()
end
function this:UpdateOwned()
OnOwnedUpdate()
end
function this:ResultAsync()
if Result then
return Result
end
ResultEvent:wait()
return Result
end
do
local function loadBalanceAsync()
local balance = UserDataModule.GetPlatformUserBalanceAsync()
SetBalance(balance)
end
local function loadDescription()
local descriptionText = packageInfo:GetDescriptionAsync()
descriptionScrollingTextBox:SetText(descriptionText or "")
end
DetailsContent.Visible = false
local loader = LoadingWidget({Parent = DetailsContainer}, {loadBalanceAsync, loadDescription})
spawn(function()
loader:AwaitFinished()
loader:Cleanup()
loader = nil
ScreenManager:DefaultFadeIn(DetailsContent)
DetailsContent.Visible = true
if inFocus then
GuiService.SelectedCoreObject = this:GetDefaultSelectableObject()
end
finishedLoading = true
end)
end
local function DoPurchase()
purchasing = true
local wasOwned = packageInfo:IsOwned()
local purchaseResult = packageInfo:BuyAsync()
local newBalance = purchaseResult and purchaseResult['balanceAfterSale'] or UserDataModule.GetPlatformUserBalanceAsync()
-- print('purchaseResult')
-- print(Utility.PrettyPrint(purchaseResult))
SetBalance(newBalance)
local nowOwns = packageInfo:IsOwned() and not wasOwned
Result = nowOwns
if nowOwns then
this:UpdateOwned()
end
purchasing = false
if not wasOwned and not nowOwns then
if ScreenManager:GetTopScreen() == this then
ScreenManager:CloseCurrent()
end
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.PackagePurchase[1]), false)
end
print("Done with purchase")
end
OnOwnedUpdate()
function this:GetDefaultSelectableObject()
return BuyButton
end
function this:FadeInBackground()
Utility.PropertyTweener(ModalBackground, "BackgroundTransparency", 1, GlobalSettings.ModalBackgroundTransparency, 0.25, Utility.EaseInOutQuad, true)
end
local currencyWidget = nil
local RobuxChangedConn = nil
function this:Show()
ModalBackground.Visible = true
ModalBackground.Parent = MyParent
local function onPackageBackgroundResize()
PackageImage.Size = Utility.CalculateFill(PackageBackground, Vector2.new(420, 420))
Utility.CalculateAnchor(PackageImage, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
end
self.PackageBackgroundChangedConn = Utility.DisconnectEvent(self.PackageBackgroundChangedConn)
self.PackageBackgroundChangedConn = PackageBackground.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
onPackageBackgroundResize()
end
end)
onPackageBackgroundResize()
if not currencyWidget then
currencyWidget = CurrencyWidgetModule({Parent = ModalBackground; Position = UDim2.new(0.052, 0, 0.88, 0); ZIndex = 4;})
end
Utility.DisconnectEvent(RobuxChangedConn)
RobuxChangedConn = currencyWidget.RobuxChanged:connect(SetBalance)
SoundManager:Play('OverlayOpen')
end
function this:Hide()
ModalBackground.Visible = false
ModalBackground.Parent = nil
self.PackageBackgroundChangedConn = Utility.DisconnectEvent(self.PackageBackgroundChangedConn)
RobuxChangedConn = Utility.DisconnectEvent(RobuxChangedConn)
end
function this:ScreenRemoved()
if currencyWidget then
currencyWidget:Destroy()
currencyWidget = nil
end
ResultEvent:fire()
end
function this:Focus()
inFocus = true
ContextActionService:BindCoreAction("ReturnFromPurchasePackageScreen",
function(actionName, inputState, inputObject)
if not purchasing then
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
end
end
end,
false,
Enum.KeyCode.ButtonB)
local buyButtonDebounce = false
self.BuyButtonConn = Utility.DisconnectEvent(self.BuyButtonConn)
self.BuyButtonConn = BuyButton.MouseButton1Click:connect(function()
if buyButtonDebounce or purchasing or not finishedLoading then
return
end
buyButtonDebounce = true
if packageInfo:IsOwned() or robuxPrice == nil then
SoundManager:Play('ButtonPress')
ScreenManager:CloseCurrent()
else
if balance then
if robuxPrice > 0 and balance < robuxPrice then
print("Goto robux screen")
EventHub:dispatchEvent(EventHub.Notifications["NavigateToRobuxScreen"])
else
local confirmPrompt = CreateConfirmPrompt({ProductName = packageName; Cost = robuxPrice; Balance = balance; ProductImage = Http.GetThumbnailUrlForAsset(packageInfo:GetAssetId()); Currency = "ROBUX"; CurrencySymbol = ""},
{ShowRemainingBalance = true; ShowRobuxIcon = true;})
confirmPrompt:SetParent(MyParent)
ScreenManager:OpenScreen(confirmPrompt)
local result = confirmPrompt:ResultAsync()
if result == true then
local loader = LoadingWidget({Parent = DetailsContainer}, {DoPurchase})
DetailsContent.Visible = false
PurchasingTitle.Visible = true
spawn(function()
loader:AwaitFinished()
loader:Cleanup()
loader = nil
ScreenManager:DefaultFadeIn(DetailsContent)
DetailsContent.Visible = true
PurchasingTitle.Visible = false
if ScreenManager:GetTopScreen() == this then
ScreenManager:CloseCurrent()
end
end)
else
print("Declined to buy")
end
end
end
end
buyButtonDebounce = false
end)
GuiService:AddSelectionParent("PurchasePackagePromptSelectionGroup", ContentContainer)
GuiService.SelectedCoreObject = self:GetDefaultSelectableObject()
end
function this:RemoveFocus()
inFocus = false
ContextActionService:UnbindCoreAction("ReturnFromPurchasePackageScreen")
GuiService:RemoveSelectionGroup("PurchasePackagePromptSelectionGroup")
self.BuyButtonConn = Utility.DisconnectEvent(self.BuyButtonConn)
GuiService.SelectedCoreObject = nil
end
function this:SetParent(parent)
MyParent = parent
ModalBackground.Parent = MyParent
end
return this
end
return CreatePurchasePackagePrompt
@@ -0,0 +1,111 @@
--[[
// ReportOverlay.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Http = require(Modules:FindFirstChild('Http'))
local ReportOverlay = {}
ReportOverlay.ReportType = {
REPORT_GAME = 0;
}
local REPORT_COMMENT = "Game reported from the Xbox App.";
function ReportOverlay:CreateReportOverlay(reportType, assetId)
local this = BaseOverlay()
local submitButton = Utility.Create'TextButton'
{
Name = "SubmitButton";
Size = UDim2.new(0, 320, 0, 66);
Position = UDim2.new(0, 776, 1, -100 - 66);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(Strings:LocalizedString("SubmitWord"));
ZIndex = this.BaseZIndex;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, submitButton.Position.X.Offset, 0, 136);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString("ReportGameWord");
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local descriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 762, 0, 304);
Position = UDim2.new(0, titleText.Position.X.Offset, 0, titleText.Position.Y.Offset + 62);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = Strings:LocalizedString("ReportPhrase");
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local reportIcon = Utility.Create'ImageLabel'
{
Name = "ReportIcon";
Position = UDim2.new(0, 226, 0, 204);
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex;
}
AssetManager.LocalImage(reportIcon, 'rbxasset://textures/ui/Shell/Icons/ErrorIconLargeCopy',
{['720'] = UDim2.new(0,214,0,176); ['1080'] = UDim2.new(0,321,0,264);})
this:SetImage(reportIcon)
submitButton.MouseButton1Click:connect(function()
if this:Close() then
if assetId then
spawn(function()
local result = Http.ReportAbuseAsync("Asset", assetId, 7, REPORT_COMMENT)
end)
end
end
end)
function this:GetPriority()
return GlobalSettings.ElevatedPriority
end
local baseFocus = this.Focus
function this:Focus()
baseFocus(this)
GuiService.SelectedCoreObject = submitButton
end
return this
end
return ReportOverlay
@@ -0,0 +1,173 @@
--[[
// RobuxBalanceOverlay.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local TextService = game:GetService('TextService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local CurrencyWidgetModule = require(Modules:FindFirstChild('CurrencyWidget'))
local UserData = require(Modules:FindFirstChild('UserData'))
local function createRobuxBalanceOverlay(platformBalance, totalBalance)
local this = BaseOverlay()
local currencyWidget = nil
local onRobuxChangedCn = nil
local overlayImage = Utility.Create'ImageLabel'
{
Name = "OverlayImage";
Size = UDim2.new(0, 416, 0, 416);
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex + 1;
Image = 'rbxasset://textures/ui/Shell/Icons/AlertIcon.png';
}
Utility.CalculateAnchor(overlayImage, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
this:SetImage(overlayImage)
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, this.RightAlign, 0, 88);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString("RobuxBalanceOverlayTitle");
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local descriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 762, 0, 126);
Position = UDim2.new(0, this.RightAlign, 0, titleText.Position.Y.Offset + 62);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = Strings:LocalizedString("RobuxBalanceOverlayPhrase");
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local platformBalanceTitle = Utility.Create'TextLabel'
{
Name = "platformBalanceTitle";
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString("PlatformBalanceTitle");
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local offsetSize = TextService:GetTextSize(platformBalanceTitle.Text, 42, GlobalSettings.RegularFont, Vector2.new())
platformBalanceTitle.Size = UDim2.new(0, offsetSize.x, 0, offsetSize.y)
Utility.CalculateAnchor(platformBalanceTitle,
UDim2.new(0, this.RightAlign, 0, descriptionText.Position.Y.Offset + descriptionText.Size.Y.Offset + 60),
Utility.Enum.Anchor.CenterLeft)
local totalBalanceTitle = platformBalanceTitle:Clone()
totalBalanceTitle.Name = "TotalBalanceTitle"
totalBalanceTitle.Text = Strings:LocalizedString("TotalBalanceTitle");
totalBalanceTitle.Parent = this.Container
offsetSize = TextService:GetTextSize(totalBalanceTitle.Text, 42, GlobalSettings.RegularFont, Vector2.new())
totalBalanceTitle.Size = UDim2.new(0, offsetSize.x, 0, offsetSize.y)
Utility.CalculateAnchor(totalBalanceTitle,
UDim2.new(0, this.RightAlign, 0, platformBalanceTitle.Position.Y.Offset + platformBalanceTitle.Size.Y.Offset + 16),
Utility.Enum.Anchor.CenterLeft)
local platformBalanceText = Utility.Create'TextLabel'
{
Name = "XboxBalanceText";
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.GreenTextColor;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local function setPlatformBalance(newBalance)
platformBalanceText.Text = Utility.FormatNumberString(newBalance)
offsetSize = TextService:GetTextSize(platformBalanceText.Text, 42, GlobalSettings.RegularFont, Vector2.new())
platformBalanceText.Size = UDim2.new(0, offsetSize.x, 0, offsetSize.y)
platformBalanceText.Position = UDim2.new(0, this.RightAlign + platformBalanceTitle.Size.X.Offset + 8, 0, platformBalanceTitle.Position.Y.Offset)
end
setPlatformBalance(platformBalance)
local totalBalanceText = platformBalanceText:Clone()
totalBalanceText.Name = "TotalBalanceText"
totalBalanceText.Parent = this.Container
local function setTotalBalance(newBalance)
totalBalanceText.Text = Utility.FormatNumberString(newBalance)
offsetSize = TextService:GetTextSize(totalBalanceText.Text, 42, GlobalSettings.RegularFont, Vector2.new())
totalBalanceText.Size = UDim2.new(0, offsetSize.x, 0, offsetSize.y)
totalBalanceText.Position = UDim2.new(0, this.RightAlign + totalBalanceTitle.Size.X.Offset + 8, 0, totalBalanceTitle.Position.Y.Offset)
end
setTotalBalance(totalBalance)
local okButton = Utility.Create'TextButton'
{
Name = "OkButton";
Size = UDim2.new(0, 320, 0, 66);
Position = UDim2.new(0, titleText.Position.X.Offset, 1, -66 - 55);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(Strings:LocalizedString("OkWord"));
ZIndex = this.BaseZIndex;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
--[[ Input Events ]]--
okButton.MouseButton1Click:connect(function()
this:Close()
end)
local baseFocus = this.Focus
function this:Focus()
baseFocus(this)
GuiService.SelectedCoreObject = okButton
-- listen to robux changing
if not currencyWidget then
currencyWidget = CurrencyWidgetModule()
end
onRobuxChangedCn = Utility.DisconnectEvent(onRobuxChangedCn)
onRobuxChangedCn = currencyWidget.RobuxChanged:connect(function(newPlatformBalance)
setPlatformBalance(newPlatformBalance)
setTotalBalance(UserData.GetTotalUserBalanceAsync())
end)
end
function this:ScreenRemoved()
onRobuxChangedCn = Utility.DisconnectEvent(onRobuxChangedCn)
currencyWidget:Destroy()
currencyWidget = nil
end
return this
end
return createRobuxBalanceOverlay
@@ -0,0 +1,262 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = {}
local ScreenStack = {}
local ScreenToHideMap = {}
local ScreenGuis = {[1] = GuiRoot}
local function ContainsScreenInternal(screen)
local foundScreenIndex = nil
for i, otherScreen in pairs(ScreenStack) do
if otherScreen == screen then
foundScreenIndex = i
end
end
return foundScreenIndex
end
local function GetScreenPriorityInternal(screen)
local priority = GlobalSettings.DefaultPriority
if screen.GetPriority ~= nil then
priority = screen:GetPriority()
end
return priority
end
function ScreenManager:GetInsertIndexForScreen(screen)
local screenPriority = GetScreenPriorityInternal(screen)
local currentScreen = self:GetTopScreen()
while currentScreen and GetScreenPriorityInternal(currentScreen) > screenPriority do
currentScreen = self:GetScreenBelow(currentScreen)
end
if currentScreen then
local currentScreenIndex = ContainsScreenInternal(currentScreen)
if currentScreenIndex then
return currentScreenIndex + 1
end
end
return 1
end
function ScreenManager:GetScreenGuiByPriority(priority)
priority = math.max(1, priority)
if not ScreenGuis[priority] then
for i = 1, priority do
if not ScreenGuis[i] then
ScreenGuis[i] = Utility.Create'ScreenGui'
{
Name = 'AppShell' .. tostring(i);
Parent = CoreGui;
}
end
end
end
return ScreenGuis[priority]
end
-- TODO: handle race conditions for opening multiple screens
local openScreenEntryCount = 0
local openScreenExitCount = 0
function ScreenManager:OpenScreen(screen, hideCurrent)
if openScreenEntryCount ~= openScreenExitCount then
print("ScreenManager: OpenScreen Re-entry detected" , openScreenEntryCount, openScreenExitCount)
end
openScreenEntryCount = openScreenEntryCount + 1
if hideCurrent == nil then
hideCurrent = true
end
local currentScreen = self:GetTopScreen()
local insertIndex = self:GetInsertIndexForScreen(screen)
local isNewTop = insertIndex > #ScreenStack
if not isNewTop then
hideCurrent = false
end
if currentScreen ~= screen then
local foundScreenIndex = ContainsScreenInternal(screen)
if foundScreenIndex then
table.remove(ScreenStack, foundScreenIndex)
end
if currentScreen then
currentScreen:RemoveFocus()
if hideCurrent then
currentScreen:Hide()
end
ScreenToHideMap[currentScreen] = hideCurrent
end
table.insert(ScreenStack, insertIndex, screen)
-- spawn(function()
if isNewTop then
if ScreenToHideMap[screen] ~= false then
screen:Show()
end
if screen == self:GetTopScreen() then
screen:Focus()
end
else
ScreenToHideMap[screen] = true
end
-- end)
end
openScreenExitCount = openScreenExitCount + 1
end
local closeCurrentEntryCount = 0
local closeCurrentExitCount = 0
function ScreenManager:CloseCurrent()
if closeCurrentEntryCount ~= closeCurrentExitCount then
print("ScreenManager: CloseScreen Re-entry detected" , closeCurrentEntryCount, closeCurrentExitCount)
end
closeCurrentEntryCount = closeCurrentEntryCount + 1
local currentScreen = ScreenStack[#ScreenStack]
local belowScreen = currentScreen and self:GetScreenBelow(currentScreen)
if currentScreen then
-- spawn(function()
currentScreen:Hide()
currentScreen:RemoveFocus()
if currentScreen.ScreenRemoved then
currentScreen:ScreenRemoved()
end
-- end)
table.remove(ScreenStack, #ScreenStack)
ScreenToHideMap[currentScreen] = nil
end
-- if belowScreen and belowScreen ~= self:GetTopScreen() then return end
local newTop = belowScreen
if newTop and newTop == self:GetTopScreen() then
local showNewTop = (ScreenToHideMap[newTop] == true)
-- spawn(function()
if showNewTop then
newTop:Show()
end
if newTop == self:GetTopScreen() then
newTop:Focus()
end
-- end)
end
closeCurrentExitCount = closeCurrentExitCount + 1
end
function ScreenManager:ContainsScreen(screen)
local index = ContainsScreenInternal(screen)
if index then
return ScreenStack[index]
end
return nil
end
function ScreenManager:GetScreenBelow(screen)
local thisScreenIndex = ContainsScreenInternal(screen)
if thisScreenIndex then
return ScreenStack[thisScreenIndex - 1]
end
return nil
end
function ScreenManager:GetTopScreen()
return ScreenStack[#ScreenStack]
end
----- TWEENS -----
local function FadeInElement(element, tweeners)
if element == nil then return end
if element:IsA('ImageLabel') or element:IsA('ImageButton') then
table.insert(tweeners, Utility.PropertyTweener(element, 'ImageTransparency', 1, element.ImageTransparency, 0.5, Utility.EaseOutQuad))
end
if element:IsA('GuiObject') then
table.insert(tweeners, Utility.PropertyTweener(element, 'BackgroundTransparency', 1, element.BackgroundTransparency, 0.5, Utility.EaseOutQuad))
end
if element:IsA('TextLabel') or element:IsA('TextBox') or element:IsA('TextButton') then
table.insert(tweeners, Utility.PropertyTweener(element, 'TextTransparency', 1, element.TextTransparency, 0.5, Utility.EaseOutQuad))
end
for _, child in pairs(element:GetChildren()) do
FadeInElement(child, tweeners)
end
end
function ScreenManager:FadeInSitu(guiObject)
local tweeners = {}
if guiObject then
FadeInElement(guiObject, tweeners)
end
return tweeners
end
function ScreenManager:DefaultFadeIn(guiObject)
local tweeners = {}
if guiObject then
table.insert(tweeners, Utility.PropertyTweener(guiObject, 'Position', guiObject.Position + UDim2.new(0.15, 0, 0, 0), guiObject.Position, 0.5,
function(t,b,c,d)
if t >= d then return b + c end
t = t / d;
local tComputed = t*(t-2)
return -UDim2.new(c.X.Scale * tComputed, c.X.Offset * tComputed, c.Y.Scale * tComputed, c.Y.Offset * tComputed) + b
end))
FadeInElement(guiObject, tweeners)
end
pcall(function()
if UserSettings().GameSettings:InStudioMode() or (PlatformService and PlatformService.DatamodelType == 0) then
local CameraManager = require(Modules:FindFirstChild('CameraManager'))
CameraManager:StartTransitionScreenEffect()
end
end)
return tweeners
end
function ScreenManager:DefaultCancelFade(tweens)
if tweens then
for _, tween in pairs(tweens) do
tween:Finish()
end
end
return nil
end
---------------
--------- SOUNDS ---------
function ScreenManager:PlayDefaultOpenSound()
SoundManager:Play('ScreenChange')
end
----------------------------
return ScreenManager
@@ -0,0 +1,480 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local GuiService = game:GetService('GuiService')
local DEFAULT_WINDOW_SIZE = UDim2.new(1,0,1,0)
local function ScrollingGrid()
local this = {}
this.Enum =
{
ScrollDirection = {["Vertical"] = 1; ["Horizontal"] = 2;};
StartCorner = {["UpperLeft"] = 1; ["UpperRight"] = 2; ["BottomLeft"] = 3; ["BottomRight"] = 4;};
--ChildAlignment = {["UpperLeft"] = 1; ["UpperRight"] = 2; ["BottomLeft"] = 3; ["BottomRight"] = 4;};
}
this.GridItems = {}
this.ItemSet = {}
this.ScrollDirection = this.Enum.ScrollDirection.Vertical
this.StartCorner = this.Enum.StartCorner.UpperLeft
this.FixedRowColumnCount = nil
--this.ChildAlignment = nil
this.CellSize = Vector2.new(100,100)
this.Padding = Vector2.new(0,0)
this.Spacing = Vector2.new(0,0)
function this:GetPadding()
return self.Padding
end
function this:SetPadding(newPadding)
if newPadding ~= self.Padding then
self.Padding = newPadding
self:RecalcLayout()
end
end
function this:GetSpacing()
return self.Spacing
end
function this:SetSpacing(newSpacing)
if newSpacing ~= self.Spacing then
self.Spacing = newSpacing
self:RecalcLayout()
end
end
function this:GetCellSize()
return self.CellSize
end
function this:SetCellSize(cellSize)
if cellSize ~= self.CellSize then
self.CellSize = cellSize
self:RecalcLayout()
end
end
function this:GetScrollDirection()
return self.ScrollDirection
end
function this:SetScrollDirection(newDirection)
if newDirection ~= self.ScrollDirection then
self.ScrollDirection = newDirection
self:RecalcLayout()
end
end
function this:GetStartCorner()
return self.StartCorner
end
function this:SetStartCorner(newStartCorner)
if newStartCorner ~= self.StartCorner then
self.StartCorner = newStartCorner
self:RecalcLayout()
end
end
function this:GetRowColumnConstraint()
return self.FixedRowColumnCount
end
function this:SetRowColumnConstraint(fixedRowColumnCount)
if fixedRowColumnCount < 1 then
fixedRowColumnCount = nil
end
if fixedRowColumnCount ~= self.FixedRowColumnCount then
self.FixedRowColumnCount = fixedRowColumnCount
self:RecalcLayout()
end
end
function this:GetClipping()
return self.Container.ClipsDescendants
end
function this:SetClipping(clippingEnabled)
self.Container.ClipsDescendants = clippingEnabled;
end
function this:GetVisible()
return self.Container.Visible
end
function this:SetVisible(isVisible)
self.Container.Visible = isVisible;
end
function this:GetSize()
return self.Container.Size
end
function this:SetSize(size)
self.Container.Size = size
end
function this:GetPosition()
return self.Container.Position
end
function this:SetPosition(position)
self.Container.Position = position
end
function this:GetParent()
return self.Container.Parent
end
function this:SetParent(parent)
self.Container.Parent = parent
end
function this:GetGuiObject()
return self.Container
end
-- Default selection handles the case of removing the last item in the grid while it is selected
-- Set to nil if do not want a default selection
function this:SetDefaultSelection(selectionObject)
self.DefaultSelection = selectionObject
end
function this:ResetDefaultSelection()
self.DefaultSelection = self.Container
end
----
function this:ContainsItem(gridItem)
return self.ItemSet[gridItem] ~= nil
end
function this:SortItems(sortFunc)
table.sort(self.GridItems, sortFunc)
self:RecalcLayout()
local selectedObject = self:FindAncestorGridItem(GuiService.SelectedCoreObject)
if selectedObject and self:ContainsItem(selectedObject) then
local thisPos = self:GetCanvasPositionForOffscreenItem(selectedObject)
if thisPos then
Utility.PropertyTweener(self.Container, 'CanvasPosition', thisPos, thisPos, 0, Utility.EaseOutQuad, true)
end
end
end
function this:AddItem(gridItem)
if not self:ContainsItem(gridItem) then
table.insert(self.GridItems, gridItem)
self.ItemSet[gridItem] = true
gridItem.Parent = self.Container
if GuiService.SelectedCoreObject == self.DefaultSelection then
GuiService.SelectedCoreObject = gridItem
end
self:RecalcLayout()
end
end
function this:RemoveItem(gridItem)
if self:ContainsItem(gridItem) then
for i, otherItem in pairs(self.GridItems) do
if otherItem == gridItem then
table.remove(self.GridItems, i)
-- Assign a new selection
if GuiService.SelectedCoreObject == gridItem then
GuiService.SelectedCoreObject = self.GridItems[i] or self.GridItems[i-1] or self.GridItems[1] or self.DefaultSelection
end
-- Clean-up
self.ItemSet[gridItem] = nil
gridItem.Parent = nil
self:RecalcLayout()
return
end
end
end
end
function this:RemoveAllItems()
local wasSelected = false
do
local currentSelection = GuiService.SelectedCoreObject
while currentSelection ~= nil and wasSelected == false do
wasSelected = wasSelected or self:ContainsItem(currentSelection)
currentSelection = currentSelection.Parent
end
end
for i = #self.GridItems, 1, -1 do
local removed = table.remove(self.GridItems, i)
self.ItemSet[removed] = nil
removed.Parent = nil
end
if wasSelected then
GuiService.SelectedCoreObject = self.Container
end
self:RecalcLayout()
self.Container.CanvasPosition = Vector2.new(0, 0)
end
function this:Get2DGridIndex(index)
-- 0 base index
local zerobasedIndex = index - 1
local rows, columns = self:GetNumRowsColumns()
local row, column;
-- TODO: implement StartCorner here
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
row = math.floor(zerobasedIndex / columns)
column = zerobasedIndex % columns
else
column = math.floor(zerobasedIndex / rows)
row = zerobasedIndex % rows
end
return row, column
end
function this:GetNumRowsColumns()
local rows, columns = 0, 0
local windowSize = self.Container.AbsoluteWindowSize
local padding = self:GetPadding()
local cellSize = self:GetCellSize()
local cellSpacing = self:GetSpacing()
local adjustedWindowSize = Utility.ClampVector2(Vector2.new(0, 0), windowSize - padding, windowSize - padding)
local absoluteCellSize = Utility.ClampVector2(Vector2.new(1,1), cellSize + cellSpacing, cellSize + cellSpacing)
local windowSizeCalc = (adjustedWindowSize + cellSpacing) / absoluteCellSize
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
columns = math.max(1, self:GetRowColumnConstraint() or math.floor(windowSizeCalc.x))
rows = math.ceil(math.max(1, #self.GridItems) / columns)
else
rows = math.max(1, self:GetRowColumnConstraint() or math.floor(windowSizeCalc.y))
columns = math.ceil(math.max(1, #self.GridItems) / rows)
end
return rows, columns
end
function this:GetGridPosition(row, column, gridItemSize)
local cellSize = self:GetCellSize()
local spacing = self:GetSpacing()
local padding = self:GetPadding()
return UDim2.new(0, padding.X + column * cellSize.X + column * spacing.X,
0, padding.Y + row * cellSize.Y + row * spacing.Y)
end
function this:GetGridItemSize()
return self.CellSize
--[[
if self.CellSize then
return self.CellSize
end
return UDim2.new(0, (self.Container.AbsoluteSize.X - ((self.Columns + 1) * self.CellPadding.X)) / self.Columns,
0, (self.Container.AbsoluteSize.Y - ((self.Rows + 1) * self.CellPadding.Y)) / self.Rows)
--]]
-- if self.ScrollDirection == EnumScrollDirection.Vertical then
-- return UDim2.new(0,(self.Container.AbsoluteSize.X - ((self.Columns + 1) * self.CellPadding.X)) / self.Columns, 0, self.Container.AbsoluteSize.Y / self.Rows)
-- else
-- return UDim2.new(0,self.Container.AbsoluteSize.Y / self.Rows, 0, (self.Container.AbsoluteSize.Y - ((self.Rows + 1) * self.CellPadding.Y)) / self.Rows)
-- end
end
function this:GetCanvasPositionForOffscreenItem(selectedObject)
-- NOTE: using <= and >= instead of < and > because scrollingframe
-- code may automatically bump it while we are observing the change
if selectedObject and self.Container and self:ContainsItem(selectedObject) then
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
if selectedObject.AbsolutePosition.Y <= self.Container.AbsolutePosition.Y then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(0, selectedObject.Position.Y.Offset)) -- - selectedObject.AbsoluteSize.Y/2))
elseif selectedObject.AbsolutePosition.Y + selectedObject.AbsoluteSize.Y >= self.Container.AbsolutePosition.Y + self.Container.AbsoluteWindowSize.Y then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(0, -(self.Container.AbsoluteWindowSize.Y - selectedObject.Position.Y.Offset - selectedObject.AbsoluteSize.Y) )) --+ selectedObject.AbsoluteSize.Y/2))
end
else -- Horizontal
if selectedObject.AbsolutePosition.X <= self.Container.AbsolutePosition.X then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(selectedObject.Position.X.Offset, 0))
elseif selectedObject.AbsolutePosition.X + selectedObject.AbsoluteSize.X >= self.Container.AbsolutePosition.X + self.Container.AbsoluteWindowSize.X then
return Utility.ClampCanvasPosition(self.Container, Vector2.new(-(self.Container.AbsoluteWindowSize.X - selectedObject.Position.X.Offset - selectedObject.AbsoluteSize.X), 0))
end
end
end
end
function this:RecalcLayout()
local padding = self:GetPadding()
local cellSpacing = self:GetSpacing()
local gridItemSize = self:GetGridItemSize()
local rows, columns = self:GetNumRowsColumns()
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
self.Container.CanvasSize = UDim2.new(self.Container.Size.X.Scale, self.Container.Size.X.Offset, 0, padding.Y * 2 + rows * gridItemSize.Y + (math.max(0, rows - 1)) * cellSpacing.Y)
else
self.Container.CanvasSize = UDim2.new(0, padding.X * 2 + columns * gridItemSize.X + (math.max(0, columns - 1)) * cellSpacing.X, self.Container.Size.Y.Scale, self.Container.Size.Y.Offset)
end
local grid2DtoIndex = {}
for i = 1, #self.GridItems do
local row, column = self:Get2DGridIndex(i)
local gridItem = self.GridItems[i]
gridItem.Size = UDim2.new(0, gridItemSize.X, 0, gridItemSize.Y)
gridItem.Position = self:GetGridPosition(row, column, gridItemSize)
grid2DtoIndex[row] = grid2DtoIndex[row] or {}
grid2DtoIndex[row][column] = gridItem
end
for rowNum, row in pairs(grid2DtoIndex) do
for columnNum, column in pairs(row) do
local gridItem = grid2DtoIndex[rowNum][columnNum]
if gridItem then
if self.ScrollDirection == self.Enum.ScrollDirection.Vertical then
gridItem.NextSelectionUp = grid2DtoIndex[rowNum - 1] and grid2DtoIndex[rowNum - 1][columnNum] or nil
gridItem.NextSelectionDown = grid2DtoIndex[rowNum + 1] and grid2DtoIndex[rowNum + 1][columnNum] or nil
if gridItem.NextSelectionDown == nil and grid2DtoIndex[rowNum + 1] ~= nil then
gridItem.NextSelectionDown = self.GridItems[#self.GridItems]
end
gridItem.NextSelectionLeft = nil
gridItem.NextSelectionRight = nil
else
gridItem.NextSelectionLeft = grid2DtoIndex[rowNum] and grid2DtoIndex[rowNum][columnNum - 1] or nil
gridItem.NextSelectionRight = grid2DtoIndex[rowNum] and grid2DtoIndex[rowNum][columnNum + 1] or nil
if gridItem.NextSelectionRight == nil and grid2DtoIndex[0] and grid2DtoIndex[0][columnNum + 1] then
gridItem.NextSelectionRight = self.GridItems[#self.GridItems]
end
gridItem.NextSelectionUp = nil
gridItem.NextSelectionDown = nil
end
end
end
end
end
function this:Destroy()
if self.Container then
self.Container:Destroy()
end
end
do
local container = Utility.Create'ScrollingFrame'
{
Size = DEFAULT_WINDOW_SIZE;
Name = "Container";
BackgroundTransparency = 1;
ClipsDescendants = true;
ScrollingEnabled = false;
ScrollBarThickness = 0;
Selectable = false;
}
this.Container = container
this.DefaultSelection = this.Container
this.Container.Changed:connect(function(prop)
if prop == 'AbsoluteSize' then
this:RecalcLayout()
end
end)
this:RecalcLayout()
function this:FindAncestorGridItem(object)
if object ~= nil then
if self:ContainsItem(object) then
return object
end
return self:FindAncestorGridItem(object.Parent)
end
end
local lastSelectedObject = nil
GuiService.Changed:connect(function(prop)
if prop == 'SelectedCoreObject' then
local selectedObject = this:FindAncestorGridItem(GuiService.SelectedCoreObject)
if selectedObject and this:ContainsItem(selectedObject) then
-- print(selectedObject.NextSelectionUp, selectedObject.NextSelectionDown, selectedObject.NextSelectionLeft, selectedObject.NextSelectionRight)
local upDirection = (this.ScrollDirection == this.Enum.ScrollDirection.Vertical) and 'NextSelectionUp' or 'NextSelectionLeft'
local downDirection = (this.ScrollDirection == this.Enum.ScrollDirection.Vertical) and 'NextSelectionDown' or 'NextSelectionRight'
local upObject = selectedObject[upDirection]
local downObject = selectedObject[downDirection]
local nextPos, upPos, downPos;
local gridItemSize = this:GetGridItemSize()
local thisPos = this:GetCanvasPositionForOffscreenItem(selectedObject)
if lastSelectedObject then
local lastUpObject = lastSelectedObject[upDirection]
local lastDownObject = lastSelectedObject[downDirection]
if upObject and lastUpObject == selectedObject then
upPos = this:GetCanvasPositionForOffscreenItem(upObject)
upPos = upPos and upPos + gridItemSize / 2
elseif downObject and lastDownObject == selectedObject then
downPos = this:GetCanvasPositionForOffscreenItem(downObject)
downPos = downPos and downPos - gridItemSize / 2
end
end
if upPos and (upPos.Y < this.Container.CanvasPosition.Y or upPos.X < this.Container.CanvasPosition.X) then
nextPos = upPos
-- print('up' , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position)
elseif downPos and (downPos.Y > this.Container.CanvasPosition.Y or downPos.X > this.Container.CanvasPosition.X) then
nextPos = downPos
-- print('down' , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position)
else
nextPos = thisPos
-- print('this' , selectedObject.Name , nextPos , selectedObject.Position, lastSelectedObject and lastSelectedObject.Position, selectedObject.AbsolutePosition, this.Container.AbsolutePosition)
end
if nextPos then
-- print("nextPos" , selectedObject.Name , nextPos)
nextPos = Utility.ClampCanvasPosition(this.Container, nextPos)
if thisPos then --and thisPos ~= nextPos then
-- Sort of a hack to not snap on the last one
if (upObject and downObject) then
Utility.PropertyTweener(this.Container, 'CanvasPosition', thisPos, thisPos, 0, Utility.EaseOutQuad, true)
end
end
Utility.PropertyTweener(this.Container, 'CanvasPosition', this.Container.CanvasPosition, nextPos, 0.2, Utility.EaseOutQuad, true)
end
lastSelectedObject = selectedObject
else
lastSelectedObject = nil
end
end
end)
end
return this
end
return ScrollingGrid
@@ -0,0 +1,209 @@
--[[
// ScrollingTextBox.lua
// Creates a scrolling text box to be used with controlers and selectable
// guis
// NOTE: Add any api needed to further expand this module
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local createScrollingTextBox = function(size, position, parent)
local this = {}
local SCROLL_BUFFER = 2
this.OnSelectableChaged = Utility.Signal()
-- adjust selection image
local edgeSelectionImage = Utility.Create'ImageLabel'
{
Name = "EdgeSelectionImage";
Size = UDim2.new(1, 32, 1, 32);
Position = UDim2.new(0, -16, 0, -16);
Image = 'rbxasset://textures/ui/SelectionBox.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(21,21,41,41);
BackgroundTransparency = 1;
}
local container = Utility.Create'Frame'
{
Name = "ScrollingTextBox";
Size = size or UDim2.new();
Position = position or UDim2.new();
BackgroundTransparency = 1;
Parent = parent;
}
local scrollingFrame = Utility.Create'ScrollingFrame'
{
Name = "ScrollingBox";
Size = UDim2.new(1, 0, 1, 0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ScrollBarThickness = 0;
SelectionImageObject = edgeSelectionImage;
Parent = container;
SoundManager:CreateSound('MoveSelection');
}
local textLabel = Utility.Create'TextLabel'
{
Name = "TextLabel";
Size = UDim2.new(1, 0, 4, 0);
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.DescriptionSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = "";
Parent = scrollingFrame;
}
local upArrow = Utility.Create'ImageLabel'
{
Name = "UpArrow";
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.WhiteTextColor;
Visible = false;
Parent = container;
}
local downArrow = Utility.Create'ImageLabel'
{
Name = "DownArrow";
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.WhiteTextColor;
Visible = false;
Parent = container;
}
AssetManager.LocalImage(upArrow,
'rbxasset://textures/ui/Shell/Icons/UpIndicatorIcon', {['720'] = UDim2.new(0,14,0,13); ['1080'] = UDim2.new(0,21,0,19);})
upArrow.Position = UDim2.new(0, 0, 1, 16)
AssetManager.LocalImage(downArrow,
'rbxasset://textures/ui/Shell/Icons/DownIndicatorIcon', {['720'] = UDim2.new(0,14,0,13); ['1080'] = UDim2.new(0,21,0,19);})
downArrow.Position = UDim2.new(0, upArrow.Size.X.Offset, 1, 16)
--[[ Private Functions ]]--
local function setArrowState()
local canvasPosition = scrollingFrame.CanvasPosition
local maxSizeY = textLabel.AbsoluteSize.y - scrollingFrame.AbsoluteWindowSize.y
if canvasPosition.y >= maxSizeY - SCROLL_BUFFER then
downArrow.ImageColor3 = GlobalSettings.GreyTextColor
else
downArrow.ImageColor3 = GlobalSettings.WhiteTextColor
end
if canvasPosition.y <= SCROLL_BUFFER then
upArrow.ImageColor3 = GlobalSettings.GreyTextColor
else
upArrow.ImageColor3 = GlobalSettings.WhiteTextColor
end
end
local function setScrollSize()
-- NOTE: this is a hack to get the actual height on textbounds
textLabel.Size = UDim2.new(1, 0, 0, 100000)
local ySize = textLabel.TextBounds.y
textLabel.Size = UDim2.new(1, 0, 0, ySize)
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, ySize)
local areArrowsVisible = ySize > scrollingFrame.AbsoluteSize.y
this:SetArrowsVisible(areArrowsVisible)
this:SetSelectable(areArrowsVisible)
setArrowState();
end
--[[ Events ]]--
scrollingFrame.Changed:connect(function(property)
if property == 'CanvasPosition' or property == 'AbsoluteWindowSize' then
setArrowState()
end
end)
--[[ Public API ]]--
function this:SetParent(newParent)
scrollingFrame.Parent = newParent
spawn(function()
setScrollSize()
end)
end
function this:SetPosition(newPosition)
scrollingFrame.Position = newPosition
end
function this:SetSize(newSize)
scrollingFrame.Size = newSize
spawn(function()
setScrollSize()
end)
end
function this:SetFontSize(newFontSize)
textLabel.FontSize = newFontSize
spawn(function()
setScrollSize()
end)
end
function this:SetFont(newFont)
textLabel.Font = newFont
spawn(function()
setScrollSize()
end)
end
function this:SetText(text)
textLabel.Text = tostring(text)
spawn(function()
setScrollSize()
end)
end
function this:SetSelectable(value)
scrollingFrame.Selectable = value
this.OnSelectableChaged:fire(value)
end
function this:SetZIndex(value)
container.ZIndex = value
textLabel.ZIndex = value
upArrow.ZIndex = value
downArrow.ZIndex = value
end
function this:SetArrowsVisible(value)
upArrow.Visible = value
downArrow.Visible = value
end
function this:GetContainer()
return container
end
function this:GetSelectableObject()
return scrollingFrame
end
function this:GetArrowsVisible()
return upArrow.Visible
end
function this:IsSelectable()
return scrollingFrame.Selectable
end
return this
end
return createScrollingTextBox
@@ -0,0 +1,277 @@
--[[
// SetAccountCredentialsScreen.lua
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService('ContextActionService')
local GuiService = game:GetService('GuiService')
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseSignInScreen = require(Modules:FindFirstChild('BaseSignInScreen'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local TextBox = require(Modules:FindFirstChild('TextBox'))
local Utility = require(Modules:FindFirstChild('Utility'))
local UserData = require(Modules:FindFirstChild('UserData'))
local function createSetAccountCredentialsScreen(title, description, buttonText)
local this = BaseSignInScreen()
this:SetTitle(string.upper(title or ""))
this:SetDescriptionText(description or "")
this:SetButtonText(buttonText or "")
local ModalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = GlobalSettings.ModalBackgroundTransparency;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local myUsername = nil
local myPassword = nil
local isUsernameValid = false
local isPasswordValid = false
local UserNameProcessingCount = 0
local PasswordProcessingCount = 0
this.UsernameObject:SetDefaultText(Strings:LocalizedString("UsernameWord").." ("..
Strings:LocalizedString("UsernameRulePhrase")..")")
this.UsernameObject:SetKeyboardTitle(Strings:LocalizedString("UsernameWord"))
this.UsernameObject:SetKeyboardDescription(Strings:LocalizedString("UsernameRulePhrase"))
local usernameChangedCn = nil
this.PasswordObject:SetDefaultText(Strings:LocalizedString("PasswordWord").." ("..
Strings:LocalizedString("PasswordRulePhrase")..")")
this.PasswordObject:SetKeyboardTitle(Strings:LocalizedString("PasswordWord"))
this.PasswordObject:SetKeyboardDescription(Strings:LocalizedString("PasswordRulePhrase"))
this.PasswordObject:SetKeyboardType(Enum.XboxKeyBoardType.Password)
local passwordChangedCn = nil
local function createAndSetCredentialsAsync()
local result = nil
local function signInAsync()
-- check linked account status
result = AccountManager:HasLinkedAccountAsync()
-- linked, set credentials
if result == AccountManager.AuthResults.Success then
result = AccountManager:SetRobloxCredentialsAsync(myUsername, myPassword)
-- unlinked, create new account and set credentials
elseif result == AccountManager.AuthResults.AccountUnlinked then
result = AccountManager:GenerateAccountAsync(myUsername, myPassword)
end
end
local loader = LoadingWidget(
{ Parent = ModalOverlay }, { signInAsync } )
-- set up full screen loader
ModalOverlay.Parent = GuiRoot
ContextActionService:BindCoreAction("BlockB", function() end, false, Enum.KeyCode.ButtonB)
local selectedObject = GuiService.SelectedCoreObject
GuiService.SelectedCoreObject = nil
-- call loader
loader:AwaitFinished()
-- clean up
loader:Cleanup()
loader = nil
GuiService.SelectedCoreObject = selectedObject
ContextActionService:UnbindCoreAction("BlockB")
ModalOverlay.Parent = nil
if result == AccountManager.AuthResults.Success then
EventHub:dispatchEvent(EventHub.Notifications["AuthenticationSuccess"])
else
local err = result and Errors.Authentication[result] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
end
local function validatePassword(silent)
PasswordProcessingCount = PasswordProcessingCount + 1
local reason = nil
isPasswordValid, reason = AccountManager:IsValidPasswordAsync(myUsername or "", myPassword)
if isPasswordValid then
if myUsername and #myUsername > 0 then
GuiService.SelectedCoreObject = this.SignInButton
else
GuiService.SelectedCoreObject = this.UsernameSelection
end
elseif isPasswordValid == false then
if not silent then
-- web returns long strings on password error. Lets create our own error type
GuiService.SelectedCoreObject = this.PasswordSelection
local err = Errors.Default;
if reason then
err = Errors.SignIn.InvalidPassword
err.Msg = reason
end
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
else -- Http failure
end
PasswordProcessingCount = PasswordProcessingCount - 1
end
local function validateUsername(silent)
UserNameProcessingCount = UserNameProcessingCount + 1
-- 1. Check if valid user name
local reason = nil
isUsernameValid, reason = AccountManager:IsValidUsernameAsync(myUsername)
if isUsernameValid then
-- 2. if password set, need to recheck password rules
if myPassword and #myPassword > 0 then
validatePassword()
else
GuiService.SelectedCoreObject = this.PasswordSelection
end
elseif isUsernameValid == false then
if not silent then
GuiService.SelectedCoreObject = this.UsernameSelection
-- NOTE: Web has changed username rules and the result of the ErrorMessage in the endpoint call.
-- We key check reason vs. our current error table, if we don't find a key, we create an error out
-- of the reason returned. If there is no reason, use default. This covers both old and new behavior
local err = nil
if Errors.SignIn[reason] then
err = Errors.SignIn[reason]
elseif reason then
err = { Title = Strings:LocalizedString("InvalidUsernameTitle"), Msg = reason }
else
err = Errors.Default
end
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
else -- Http Request failed
end
UserNameProcessingCount = UserNameProcessingCount - 1
end
local function onUsernameChanged(text)
myUsername = text
if #myUsername > 0 then
validateUsername()
else
GuiService.SelectedCoreObject = this.UsernameSelection
isUsernameValid = false
end
end
local function onPasswordChanged(text)
myPassword = text
if #myPassword > 0 then
validatePassword()
else
GuiService.SelectedCoreObject = this.PasswordSelection
isPasswordValid = false
end
end
local isSettingCredentials = false
this.SignInButton.MouseButton1Click:connect(function()
if isSettingCredentials then return end
isSettingCredentials = true
local function stillValidatingUserInfo()
return UserNameProcessingCount > 0 or PasswordProcessingCount > 0
end
local function awaitValidatingUserInfo()
while stillValidatingUserInfo() do wait() end
end
SoundManager:Play('ButtonPress')
local processingFunctions = nil
-- Wait for current validation to finish
if stillValidatingUserInfo() then
processingFunctions = { awaitValidatingUserInfo }
-- Retry our username and password validation
elseif isUsernameValid == nil or isPasswordValid == nil then
processingFunctions = {}
if isUsernameValid == nil then
table.insert(processingFunctions, function() validateUsername(true) awaitValidatingUserInfo() end)
end
if isPasswordValid == nil then
table.insert(processingFunctions, function() validatePassword(true) awaitValidatingUserInfo() end)
end
end
if processingFunctions then
local processingLoader = LoadingWidget(
{ Parent = ModalOverlay },
processingFunctions)
-- NOTE: may need to get a separate overlay for this spinner
-- Also should we disable input while overlay is active?
ModalOverlay.Parent = GuiRoot
processingLoader:AwaitFinished()
processingLoader:Cleanup()
processingLoader = nil
ModalOverlay.Parent = nil
end
if isUsernameValid and isPasswordValid then
createAndSetCredentialsAsync()
elseif isUsernameValid == false or isPasswordValid == false then
local err = Errors.SignIn.NoUsernameOrPasswordEntered
ScreenManager:OpenScreen(ErrorOverlay(err), false)
else -- Http failed to validate your password or username
local err = Errors.SignIn.ConnectionFailed
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
isSettingCredentials = false
end)
--[[ Public API ]]--
-- override
local baseFocus = this.Focus
function this:Focus()
baseFocus(self)
usernameChangedCn = this.UsernameObject.OnTextChanged:connect(onUsernameChanged)
passwordChangedCn = this.PasswordObject.OnTextChanged:connect(onPasswordChanged)
end
-- override
local baseRemoveFocus = this.RemoveFocus
function this:RemoveFocus()
baseRemoveFocus(self)
Utility.DisconnectEvent(usernameChangedCn)
Utility.DisconnectEvent(passwordChangedCn)
end
return this
end
return createSetAccountCredentialsScreen
@@ -0,0 +1,242 @@
--[[
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseScreen = require(Modules:FindFirstChild('BaseScreen'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local AccountScreen = require(Modules:FindFirstChild('AccountScreen'))
local function createSettingsScreen()
local this = BaseScreen()
this:SetTitle(string.upper(Strings:LocalizedString("SettingsWord")))
local VersionBuildIdText = Utility.Create'TextLabel'
{
Name = "VersionBuildIdText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Right;
TextYAlignment = Enum.TextYAlignment.Bottom;
Text = '';
Parent = this.Container;
}
do
local versionInfo;
if UserSettings().GameSettings:InStudioMode() then
versionInfo = {Major = 1, Minor = 0, Build = 0, Revision = 0}
elseif PlatformService then
versionInfo = PlatformService:GetVersionIdInfo();
else
versionInfo = {Major = 1, Minor = 1, Build = 1, Revision = 1}
end
local versionStr = string.format(Strings:LocalizedString('VersionIdString'), tostring(versionInfo['Major']) , tostring(versionInfo['Minor']), tostring(versionInfo['Build']), tostring(versionInfo['Revision']))
VersionBuildIdText.Text = versionStr
end
local spacing = 40
local DefaultTransparency = GlobalSettings.TextBoxDefaultTransparency
local SelectedTransparency = GlobalSettings.TextBoxSelectedTransparency
local AccountButton = Utility.Create'TextButton'
{
Name = "AccountButton";
Size = UDim2.new(0, 394, 0, 612);
Position = UDim2.new(0, 0, 0, 238);
BackgroundTransparency = DefaultTransparency;
BackgroundColor3 = GlobalSettings.TextBoxColor;
BorderSizePixel = 0;
Text = "";
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
local SwitchProfileButton = AccountButton:Clone()
SwitchProfileButton.Name = "SwitchProfileButton"
SwitchProfileButton.Position = UDim2.new(0, AccountButton.Size.X.Offset + spacing, 0, 238)
SwitchProfileButton.Parent = this.Container
local OverscanButton = SwitchProfileButton:Clone()
OverscanButton.Name = "OverscanButton"
OverscanButton.Position = UDim2.new(0, SwitchProfileButton.Position.X.Offset + SwitchProfileButton.Size.X.Offset + spacing, 0, 238)
OverscanButton.Parent = this.Container
local HelpButton = OverscanButton:Clone()
HelpButton.Name = "HelpButton"
HelpButton.Position = UDim2.new(0, OverscanButton.Position.X.Offset + OverscanButton.Size.X.Offset + spacing, 0, 238)
HelpButton.Parent = this.Container
local AccountIcon = Utility.Create'ImageLabel'
{
Name = "AccountIcon";
Size = UDim2.new(0, 256, 0, 256);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/AccountIcon.png';
Parent = AccountButton;
}
Utility.CalculateAnchor(AccountIcon, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
local AccountText = Utility.Create'TextLabel'
{
Name = "AccountText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0.5, 0, 1, -96);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString("AccountWord");
Parent = AccountButton;
}
local SwitchProfileIcon = Utility.Create'ImageLabel'
{
Name = "SwitchProfileIcon";
Size = UDim2.new(0, 224, 0, 255);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/ProfileIcon.png';
Parent = SwitchProfileButton;
}
Utility.CalculateAnchor(SwitchProfileIcon, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
SwitchProfileText = AccountText:Clone()
SwitchProfileText.Name = "SwitchProfileText"
SwitchProfileText.Text = Strings:LocalizedString("SwitchProfileWord");
SwitchProfileText.Parent = SwitchProfileButton
local OverscanIcon = Utility.Create'ImageLabel'
{
Name = "OverscanIcon";
Size = UDim2.new(0, 256, 0, 181);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/TVIcon.png';
Parent = OverscanButton;
}
Utility.CalculateAnchor(OverscanIcon, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
OverscanText = AccountText:Clone()
OverscanText.Name = "OverscanText";
OverscanText.Text = Strings:LocalizedString("OverscanWord");
OverscanText.Parent = OverscanButton
local HelpIcon = Utility.Create'ImageLabel'
{
Name = "HelpIcon";
Size = UDim2.new(0, 256, 0, 256);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/HelpIcon.png';
Parent = HelpButton;
}
Utility.CalculateAnchor(HelpIcon, UDim2.new(0.5, 0, 0.5, 0), Utility.Enum.Anchor.Center)
HelpText = AccountText:Clone()
HelpText.Name = "HelpText";
HelpText.Text = Strings:LocalizedString("HelpWord");
HelpText.Parent = HelpButton
AccountButton.SelectionGained:connect(function()
Utility.PropertyTweener(AccountButton, "BackgroundTransparency", SelectedTransparency,
SelectedTransparency, 0, Utility.EaseInOutQuad, true)
end)
AccountButton.SelectionLost:connect(function()
AccountButton.BackgroundTransparency = DefaultTransparency
end)
SwitchProfileButton.SelectionGained:connect(function()
Utility.PropertyTweener(SwitchProfileButton, "BackgroundTransparency", SelectedTransparency,
SelectedTransparency, 0, Utility.EaseInOutQuad, true)
end)
SwitchProfileButton.SelectionLost:connect(function()
SwitchProfileButton.BackgroundTransparency = DefaultTransparency
end)
OverscanButton.SelectionGained:connect(function()
Utility.PropertyTweener(OverscanButton, "BackgroundTransparency", SelectedTransparency,
SelectedTransparency, 0, Utility.EaseInOutQuad, true)
end)
OverscanButton.SelectionLost:connect(function()
OverscanButton.BackgroundTransparency = DefaultTransparency
end)
HelpButton.SelectionGained:connect(function()
Utility.PropertyTweener(HelpButton, "BackgroundTransparency", SelectedTransparency,
SelectedTransparency, 0, Utility.EaseInOutQuad, true)
end)
HelpButton.SelectionLost:connect(function()
HelpButton.BackgroundTransparency = DefaultTransparency
end)
--[[ Input ]]--
AccountButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
local accountScreen = AccountScreen()
if accountScreen then
accountScreen:SetParent(this.Container.Parent)
ScreenManager:OpenScreen(accountScreen, true)
else
ScreenManager:OpenScreen(ErrorOverlay(Errors.Default), false)
end
end)
local switchProfileDebounce = false
SwitchProfileButton.MouseButton1Click:connect(function()
if switchProfileDebounce then return end
switchProfileDebounce = true
SoundManager:Play('ButtonPress')
if UserSettings().GameSettings:InStudioMode() then
ScreenManager:OpenScreen(ErrorOverlay(Errors.Test.FeatureNotAvailableInStudio), false)
elseif PlatformService then
PlatformService:PopupAccountPickerUI(Enum.UserInputType.Gamepad1)
end
switchProfileDebounce = false
end)
local overscanDebounce = false
OverscanButton.MouseButton1Click:connect(function()
if overscanDebounce then return end
overscanDebounce = true
SoundManager:Play('ButtonPress')
EventHub:dispatchEvent(EventHub.Notifications["OpenOverscanScreen"], "")
overscanDebounce = false
end)
local helpDebounce = false
HelpButton.MouseButton1Click:connect(function()
if helpDebounce then return end
helpDebounce = true
SoundManager:Play('ButtonPress')
if UserSettings().GameSettings:InStudioMode() then
ScreenManager:OpenScreen(ErrorOverlay(Errors.Test.FeatureNotAvailableInStudio), false)
helpDebounce = false
else
local success, result = pcall(function()
-- errors will be handled by xbox
return PlatformService:PopupHelpUI()
end)
helpDebounce = false
end
end)
--[[ Public API ]]--
--Override
function this:GetDefaultSelectionObject()
return AccountButton
end
return this
end
return createSettingsScreen
@@ -0,0 +1,172 @@
--[[
// SideBar.lua
// Creates a side bar to be used for certain pages
// Currently used by:
// GameGenre
// Friends
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService('GuiService')
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local CreateSideBar = function()
local this = {}
local buttons = {}
local selectedObject = nil
local INSET_Y = 156
local INSET_X = 65
local BUTTON_SIZE_Y = 75
local modalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local container = Utility.Create'Frame'
{
Name = "SideBarContainer";
Size = UDim2.new(0.3, 0, 1, 0);
Position = UDim2.new(1, 0, 0, 0);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.OverlayColor;
ZIndex = 5;
Parent = modalOverlay;
}
local dummySelectionImage = Utility.Create'TextButton'
{
Name = "DummySelectionImage";
Size = UDim2.new(0, 0, 0, 0);
Visible = false;
SoundManager:CreateSound('MoveSelection');
}
local function recalcPositions()
for i = 1, #buttons do
buttons[i].Position = UDim2.new(0, 0, 0, INSET_Y + (BUTTON_SIZE_Y * (i - 1)))
end
end
--[[ Public API ]]--
function this:AddItem(newItemName, callback)
local button = Utility.Create'TextButton'
{
Name = "SortButton";
Size = UDim2.new(1, 0, 0, BUTTON_SIZE_Y);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
BackgroundTransparency = 1;
ZIndex = 6;
Text = "";
SelectionImageObject = dummySelectionImage;
Parent = container;
SoundManager:CreateSound('MoveSelection');
}
local text = Utility.Create'TextLabel'
{
Name = "SortName";
Size = UDim2.new(1, -INSET_X, 1, 0);
Position = UDim2.new(0, INSET_X, 0, 0);
BackgroundTransparency = 1;
Text = newItemName;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.MediumFontSize;
ZIndex = 7;
Parent = button;
}
button.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
ScreenManager:CloseCurrent()
callback()
end)
button.SelectionGained:connect(function()
button.BackgroundTransparency = 0
text.TextColor3 = GlobalSettings.TextSelectedColor
end)
button.SelectionLost:connect(function()
button.BackgroundTransparency = 1
text.TextColor3 = GlobalSettings.WhiteTextColor
end)
buttons[#buttons + 1] = button
recalcPositions()
end
function this:RemoveAllItems()
for i,button in pairs(buttons) do
button:Destroy()
buttons[i] = nil
end
end
function this:SetSelectedObject(indexToOpenTo)
if #buttons > 0 then
selectedObject = indexToOpenTo and buttons[indexToOpenTo] or buttons[1]
else
selectedObject = nil
end
end
function this:Show()
modalOverlay.Parent = GuiRoot
local tweenIn = Utility.PropertyTweener(modalOverlay, "BackgroundTransparency", 1,
GlobalSettings.ModalBackgroundTransparency, 0.25, Utility.EaseInOutQuad, true, nil)
container:TweenPosition(UDim2.new(1 - container.Size.X.Scale, 0, 0, 0),
Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.25, true)
SoundManager:Play('SideMenuSlideIn')
end
function this:Hide()
local tweenOut = Utility.PropertyTweener(modalOverlay, "BackgroundTransparency", 0.3, 1, 0.25, Utility.EaseInOutQuad, true,
function()
modalOverlay.Parent = nil
end)
container:TweenPosition(UDim2.new(1, 0, 0, 0), Enum.EasingDirection.InOut, Enum.EasingStyle.Sine, 0.25, true)
end
function this:Focus()
GuiService:AddSelectionParent("SideBar", container)
if selectedObject then
GuiService.SelectedCoreObject = selectedObject
else
self:SetSelectedObject(1)
GuiService.SelectedCoreObject = selectedObject
end
-- connect back button
ContextActionService:BindCoreAction("CloseSideBar",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
end
end,
false, Enum.KeyCode.ButtonB)
end
function this:RemoveFocus()
GuiService:RemoveSelectionGroup("SideBar")
ContextActionService:UnbindCoreAction("CloseSideBar")
selectedObject = nil
end
return this
end
return CreateSideBar
@@ -0,0 +1,197 @@
--[[
// SignInScreen.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local TextService = game:GetService('TextService')
local UserInputService = game:GetService('UserInputService')
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local LinkAccountScreen = require(Modules:FindFirstChild('LinkAccountScreen'))
local SetAccountCredentialsScreen = require(Modules:FindFirstChild('SetAccountCredentialsScreen'))
local function createSignInScreen()
local this = {}
local isFocused = false
local DefaultButtonColor = GlobalSettings.GreyButtonColor
local SelectedButtonColor = GlobalSettings.GreySelectedButtonColor
local DefaultButtonTextColor = GlobalSettings.WhiteTextColor
local SelectedButtonTextColor = GlobalSettings.TextSelectedColor
-- get gamertag and set display text
local createAccountText = Strings:LocalizedString("PlayAsPhrase")
if PlatformService then
local userInfo = PlatformService:GetPlatformUserInfo()
local gamertag = userInfo["Gamertag"]
if gamertag then
createAccountText = string.format(createAccountText, gamertag)
end
end
local ModalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = GlobalSettings.ModalBackgroundTransparency;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local Container = Utility.Create'Frame'
{
Name = "SignInScreen";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
}
local RobloxLogo = Utility.Create'ImageLabel'
{
Name = "RobloxLogo";
Size = UDim2.new(0, 594, 0, 199);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/ROBLOXSplashLogo.png';
Parent = Container;
}
Utility.CalculateAnchor(RobloxLogo, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
local FadeInFrame = Utility.Create'Frame'
{
Name = "FadeInFrame";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
Parent = Container;
}
local LinkAccountButton = Utility.Create'ImageButton'
{
Name = "LinkAccountButton";
Size = UDim2.new(0, 200, 0, 64);
Position = UDim2.new(0.5, -100, 1, -64 - 140);
BackgroundTransparency = 1;
ImageColor3 = DefaultButtonColor;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
ZIndex = 2;
Parent = FadeInFrame;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1)
}
local CreateAccountButton = LinkAccountButton:Clone()
CreateAccountButton.Name = "CreateAccountButton"
CreateAccountButton.Size = UDim2.new(0, 360, 0, 64);
CreateAccountButton.Position = UDim2.new(0.5, -180, 1, LinkAccountButton.Position.Y.Offset - 64 - 44)
CreateAccountButton.Parent = FadeInFrame
local LinkAccountText = Utility.Create'TextLabel'
{
Name = "LinkAccountText";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = DefaultButtonTextColor;
Text = string.upper(Strings:LocalizedString("SignInPhrase"));
ZIndex = 2;
Parent = LinkAccountButton;
}
local CreateAccountText = LinkAccountText:Clone()
CreateAccountText.Text = string.upper(createAccountText)
CreateAccountText.Parent = CreateAccountButton
local createAccountTextSize = TextService:GetTextSize(createAccountText, Utility.ConvertFontSizeEnumToInt(CreateAccountText.FontSize),
CreateAccountText.Font, Vector2.new(0, 0))
CreateAccountButton.Size = UDim2.new(0, createAccountTextSize.X + 64, 0, CreateAccountButton.Size.Y.Offset)
CreateAccountButton.Position = UDim2.new(0.5, -CreateAccountButton.Size.X.Offset/2, 1, CreateAccountButton.Position.Y.Offset)
CreateAccountButton.SelectionGained:connect(function()
CreateAccountButton.ImageColor3 = SelectedButtonColor
CreateAccountText.TextColor3 = SelectedButtonTextColor
end)
CreateAccountButton.SelectionLost:connect(function()
CreateAccountButton.ImageColor3 = DefaultButtonColor
CreateAccountText.TextColor3 = DefaultButtonTextColor
end)
LinkAccountButton.SelectionGained:connect(function()
LinkAccountButton.ImageColor3 = SelectedButtonColor
LinkAccountText.TextColor3 = SelectedButtonTextColor
end)
LinkAccountButton.SelectionLost:connect(function()
LinkAccountButton.ImageColor3 = DefaultButtonColor
LinkAccountText.TextColor3 = DefaultButtonTextColor
end)
local function animateOnShow()
ScreenManager:DefaultCancelFade(this.TransitionTweens)
FadeInFrame.Visible = true
this.TransitionTweens = ScreenManager:FadeInSitu(FadeInFrame)
if isFocused then
GuiService.SelectedCoreObject = CreateAccountButton
end
end
--[[ Input ]]--
CreateAccountButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
local setAccountCredentialsScreen = SetAccountCredentialsScreen(Strings:LocalizedString("SignUpTitle"),
Strings:LocalizedString("SignUpPhrase"), Strings:LocalizedString("SignUpWord"))
setAccountCredentialsScreen:SetParent(Container.Parent)
ScreenManager:OpenScreen(setAccountCredentialsScreen, true)
end)
LinkAccountButton.MouseButton1Click:connect(function()
SoundManager:Play('ButtonPress')
local linkAccountScreen = LinkAccountScreen()
linkAccountScreen:SetParent(Container.Parent)
ScreenManager:OpenScreen(linkAccountScreen, true)
end)
--[[ Public API ]]--
function this:SetParent(newParent)
Container.Parent = newParent
end
function this:Show()
Utility.CalculateAnchor(RobloxLogo, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center)
Container.Visible = true
animateOnShow()
end
function this:Hide()
Container.Visible = false
FadeInFrame.Visible = false
end
function this:Focus()
isFocused = true
GuiService:AddSelectionParent("SignInScreen", FadeInFrame)
GuiService.SelectedCoreObject = CreateAccountButton
end
function this:RemoveFocus()
isFocused = false
GuiService:RemoveSelectionGroup("SignInScreen")
GuiService.SelectedCoreObject = nil
end
return this
end
return createSignInScreen
@@ -0,0 +1,244 @@
--[[
// SocialPane.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local Utility = require(Modules:FindFirstChild('Utility'))
local FriendsData = require(Modules:FindFirstChild('FriendsData'))
local FriendsView = require(Modules:FindFirstChild('FriendsView'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScrollingGridModule = require(Modules:FindFirstChild('ScrollingGrid'))
local SocialScreenModule = require(Modules:FindFirstChild('SocialScreen'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
--[[ Constants ]]--
local GRID_SIZE = UDim2.new(1, 0, 1, 0)
local GRID_ROWS = 5
local GRID_COLUMNS = 4
local GRID_DIRECTION = 'Horizontal'
local function CreateSocialPane(parent)
local this = {}
local BREAK_COLOR = Color3.new(78/255, 78/255, 78/255)
local DISPLAY_FRIEND_COUNT = 15
local SIDE_BAR_ITEMS = {
string.upper(Strings:LocalizedString("JoinGameWord"));
string.upper(Strings:LocalizedString("ViewGameDetailsWord"));
string.upper(Strings:LocalizedString("InviteToPartyWord"));
string.upper(Strings:LocalizedString("ViewGamerCardWord"));
}
local moreFriendsScreen = nil
local myFriendsView = nil
local isPaneFocused = false
local defaultSelectionObject = nil
local noSelectionObject = Utility.Create'ImageLabel'
{
BackgroundTransparency = 1;
}
local SocialPaneContainer = Utility.Create'Frame'
{
Name = 'SocialPane';
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
SelectionImageObject = noSelectionObject;
Parent = parent;
}
--[[ Online Friends ]]--
local onlineFriendsTitle = Utility.Create'TextLabel'
{
Name = "OnlineFriendsTitle";
Size = UDim2.new(0, 0, 0, 33);
Position = UDim2.new();
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Text = string.upper(Strings:LocalizedString("OnlineFriendsWords"));
Visible = false;
Parent = SocialPaneContainer;
}
local onlineFriendsContainer = Utility.Create'Frame'
{
Name = "OnlineFriendsContainer";
Size = UDim2.new(0, 1438, 0, 610);
Position = UDim2.new(0, 0, 0, onlineFriendsTitle.Size.Y.Offset);
BackgroundTransparency = 1;
Parent = SocialPaneContainer;
}
local moreFriendsButton = Utility.Create'ImageButton'
{
Name = "MoreButton";
BackgroundTransparency = 1;
SoundManager:CreateSound('MoveSelection');
}
AssetManager.LocalImage(moreFriendsButton,
'rbxasset://textures/ui/Shell/Buttons/MoreButton', {['720'] = UDim2.new(0,67,0,28); ['1080'] = UDim2.new(0,100,0,42);})
moreFriendsButton.Position = UDim2.new(1, -moreFriendsButton.Size.X.Offset, 1, 12)
local function updateMoreImage(isSelected)
local uri = isSelected and 'rbxasset://textures/ui/Shell/Buttons/MoreButtonSelected'
or 'rbxasset://textures/ui/Shell/Buttons/MoreButton'
AssetManager.LocalImage(moreFriendsButton, uri, {['720'] = UDim2.new(0,72,0,33); ['1080'] = UDim2.new(0,108,0,50);})
end
moreFriendsButton.SelectionGained:connect(function()
updateMoreImage(true)
end)
moreFriendsButton.SelectionLost:connect(function()
updateMoreImage(false)
end)
local friendsScrollingGrid = ScrollingGridModule()
friendsScrollingGrid:SetSize(UDim2.new(1, 0, 1, 0))
friendsScrollingGrid:SetCellSize(Vector2.new(446, 114))
friendsScrollingGrid:SetSpacing(Vector2.new(50, 10))
friendsScrollingGrid:SetScrollDirection(friendsScrollingGrid.Enum.ScrollDirection.Horizontal)
friendsScrollingGrid:SetPosition(UDim2.new(0, 0, 0, 0))
local friendsScrollingGridContainer = friendsScrollingGrid:GetGuiObject()
friendsScrollingGridContainer.Visible = false
friendsScrollingGrid:SetParent(onlineFriendsContainer)
--[[ No Friends Online ]]--
local noFriendsIcon = Utility.Create'ImageLabel'
{
Name = "noFriendsIcon";
Size = UDim2.new(0, 296, 0, 259);
Position = UDim2.new(0.5, -296/2, 0, 100);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/FriendsIcon@1080.png';
Visible = false;
Parent = SocialPaneContainer;
}
local noFriendsText = Utility.Create'TextLabel'
{
Name = "NoFriendsText";
Size = UDim2.new(0, 500, 0, 72);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = Strings:LocalizedString("NoFriendsPhrase");
TextYAlignment = Enum.TextYAlignment.Top;
TextWrapped = true;
Visible = false;
Parent = SocialPaneContainer;
}
noFriendsText.Position = UDim2.new(0.5, -noFriendsText.Size.X.Offset/2, 0,
noFriendsIcon.Position.Y.Offset + noFriendsIcon.Size.Y.Offset + 32)
--[[ Content Functions ]]--
local function setPaneContentVisible(hasOnlineFriends)
noFriendsIcon.Visible = not hasOnlineFriends
noFriendsText.Visible = not hasOnlineFriends
--
onlineFriendsTitle.Visible = hasOnlineFriends
--
defaultSelectionObject = hasOnlineFriends and myFriendsView:GetDefaultFocusItem() or nil
end
local function onFriendsUpdated(friendCount)
local hasOnlineFriends = friendCount > 0
setPaneContentVisible(hasOnlineFriends)
if hasOnlineFriends then
if friendCount > DISPLAY_FRIEND_COUNT and not moreFriendsButton.Parent then
moreFriendsButton.Parent = onlineFriendsContainer
elseif friendCount < DISPLAY_FRIEND_COUNT and moreFriendsButton.Parent then
moreFriendsButton.Parent = nil
end
end
end
local function loadFriendsView()
local friendsData = FriendsData.GetOnlineFriendsAsync()
local displayCount = math.min(#friendsData, DISPLAY_FRIEND_COUNT)
myFriendsView = FriendsView(friendsScrollingGrid, friendsData, DISPLAY_FRIEND_COUNT, onFriendsUpdated)
onFriendsUpdated(#friendsData)
if isPaneFocused then
GuiService.SelectedCoreObject = defaultSelectionObject
end
moreFriendsScreen = SocialScreenModule(Strings:LocalizedString("OnlineFriendsWords"), Strings:LocalizedString("FriendsWord"))
moreFriendsButton.MouseButton1Click:connect(function()
EventHub:dispatchEvent(EventHub.Notifications["OpenSocialScreen"], moreFriendsScreen);
end)
end
local loader = LoadingWidget(
{ Parent = SocialPaneContainer }, { loadFriendsView }
)
spawn(function()
loader:AwaitFinished()
loader:Cleanup()
friendsScrollingGridContainer.Visible = true
end)
function this:GetName()
return Strings:LocalizedString('FriendsWord')
end
function this:IsFocused()
return isPaneFocused
end
--[[ Public API ]]--
function this:Show()
SocialPaneContainer.Visible = true
self.TransitionTweens = ScreenManager:DefaultFadeIn(SocialPaneContainer)
ScreenManager:PlayDefaultOpenSound()
end
function this:Hide()
SocialPaneContainer.Visible = false
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
end
function this:Focus()
-- TODO: Hook in the hidden selection after figuring how how to know how
-- panes take focus (ie, bumper, tab, etc)
isPaneFocused = true
if defaultSelectionObject then
GuiService.SelectedCoreObject = defaultSelectionObject
end
end
function this:RemoveFocus()
isPaneFocused = false
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(SocialPaneContainer) then
GuiService.SelectedCoreObject = nil
end
end
function this:SetPosition(newPosition)
SocialPaneContainer.Position = newPosition
end
function this:SetParent(newParent)
SocialPaneContainer.Parent = newParent
end
function this:IsAncestorOf(object)
return SocialPaneContainer and SocialPaneContainer:IsAncestorOf(object)
end
return this
end
return CreateSocialPane
@@ -0,0 +1,152 @@
--[[
// SocialMorePane.lua
// Shows a full grid view of a social view. Only shown if social items for a given
// view are above a threshold set by SocialPane.lua
// User by SocialPane.lua
// TODO:
Bug with TabDock where its removing focus of the last tab when re-entering the tab dock
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService("GuiService")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScrollingGridModule = require(Modules:FindFirstChild('ScrollingGrid'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local FriendsData = require(Modules:FindFirstChild('FriendsData'))
local FriendsView = require(Modules:FindFirstChild('FriendsView'))
local createSocialScreen = function(currentScreenTitle, previousScreenName)
local this = {}
local mySocialView = nil
local container = Utility.Create'Frame'
{
Name = "SocialContainer";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Visible = false;
}
local backLabel = Utility.Create'ImageLabel'
{
-- PLACE HOLDER
Name = "BackLabel";
Position = UDim2.new(0, 0, 0, 0);
BackgroundTransparency = 1;
ZIndex = 2;
Parent = container;
}
AssetManager.LocalImage(backLabel,
'rbxasset://textures/ui/Shell/Icons/BackIcon', {['720'] = UDim2.new(0,32,0,32); ['1080'] = UDim2.new(0,48,0,48);})
-- Right now previous screen is always Friends, but we're still using previousScreenName in case
-- recently played with ever makes it in
local backText = Utility.Create'TextLabel'
{
Name = "BackText";
Size = UDim2.new(0, 0, 0, backLabel.Size.Y.Offset);
Position = UDim2.new(0, backLabel.Size.X.Offset + 8, 0, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextXAlignment = Enum.TextXAlignment.Left;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = string.upper(previousScreenName);
Parent = container;
}
local titleLabel = Utility.Create'TextLabel'
{
Name = "TitleLabel";
Size = UDim2.new(0, 0, 0, 35);
Position = UDim2.new(0, 16, 0, backLabel.Size.Y.Offset + 74);
BackgroundTransparency = 1;
Text = string.upper(currentScreenTitle);
TextColor3 = GlobalSettings.WhiteTextColor;
TextXAlignment = Enum.TextXAlignment.Left;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.HeaderSize;
Parent = container;
}
local socialScrollingGrid = ScrollingGridModule()
socialScrollingGrid:SetSize(UDim2.new(0, 1438, 0, 610))
socialScrollingGrid:SetCellSize(Vector2.new(446, 114))
socialScrollingGrid:SetSpacing(Vector2.new(50, 10))
socialScrollingGrid:SetScrollDirection(socialScrollingGrid.Enum.ScrollDirection.Horizontal)
socialScrollingGrid:SetPosition(UDim2.new(0, 0, 0, titleLabel.Position.Y.Offset + titleLabel.Size.Y.Offset + 90))
socialScrollingGrid:SetClipping(false)
socialScrollingGrid:SetParent(container)
--[[ Set Images ]]--
local function setSocialView()
print('set the social screen data')
local friendsData = FriendsData.GetOnlineFriendsAsync()
mySocialView = FriendsView(socialScrollingGrid, friendsData, nil, nil)
end
--[[ Public API ]]--
function this:Show()
container.Visible = true
self.TransitionTweens = ScreenManager:DefaultFadeIn(container)
ScreenManager:PlayDefaultOpenSound()
end
function this:Hide()
container.Visible = false
container.Parent = nil
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
end
function this:Focus()
if self.SavedSelectedObject and self.SavedSelectedObject:IsDescendantOf(container) then
GuiService.SelectedCoreObject = self.SavedSelectedObject
else
if mySocialView then
GuiService.SelectedCoreObject = mySocialView:GetDefaultFocusItem()
end
end
--
ContextActionService:BindCoreAction("ReturnFromSocialScreen",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.End then
ScreenManager:CloseCurrent()
end
end,
false, Enum.KeyCode.ButtonB)
end
function this:RemoveFocus()
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(container) then
self.SavedSelectedObject = selectedObject
GuiService.SelectedCoreObject = nil
else
self.SavedSelectedObject = nil
end
ContextActionService:UnbindCoreAction("ReturnFromSocialScreen")
end
function this:SetPosition(newPosition)
container.Position = newPosition
end
function this:SetParent(newParent)
container.Parent = newParent
end
setSocialView()
return this
end
return createSocialScreen
@@ -0,0 +1,202 @@
--[[
// SortCarousel.lua
// Creates a sort carousel with data for the sort
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GameDataModule = require(Modules:FindFirstChild('GameData'))
local ImageSlider = require(Modules:FindFirstChild('ImageSlider'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ThumbnailLoader = require(Modules:FindFirstChild('ThumbnailLoader'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local createSortCarousel = function(size, position, gameCollection, parent)
local this = {}
--[[ Constants ]]--
local LOAD_COUNT = 20
local IMAGE_COUNT = 9
local carousel = ImageSlider(size, position)
carousel:SetPadding(18)
carousel:SetParent(parent)
--[[ Variables ]]--
local images = {}
local gameData = {}
local lastSelectedIndex = nil
local lastLoadedCount = 0
--[[ Event ]]--
this.OnNewGameSelected = Utility.Signal()
-- we need to fetch for description/isFavorited, so we send a late signal to update those
this.OnNewGameSelectedLate = Utility.Signal()
--[[ Private Functions ]]--
local function insertSortData(page)
if not page then
print("Error: SortCarousel failed to get a valid page for insertSortData.")
return
end
-- TODO: Get new square icons
local ids = page:GetPagePlaceIds()
local names = page:GetPagePlaceNames()
local voteData = page:GetPageVoteData()
local iconIds = page:GetPageIconIds()
local creatorNames = page:GetCreatorNames()
--
for i = 1, #page.Data do
local entry = {
Name = names[i];
PlaceId = ids[i];
IconId = iconIds[i];
VoteData = voteData[i];
CreatorName = creatorNames[i];
-- Description/IsFavorited needs to be queried for each game, do it only when needed
-- we'll also cache gameData
Description = nil;
IsFavorited = nil;
GameData = nil;
}
gameData[#gameData + 1] = entry
end
lastLoadedCount = #gameData
end
local function createCarouselImages()
local thumbSize = ThumbnailLoader.Sizes.Medium
local assetType = ThumbnailLoader.AssetType.Icon
for i = 1, IMAGE_COUNT do
local data = gameData[i]
if data then
local image = Utility.Create'ImageButton'
{
Name = "GameImage";
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.GreyButtonColor;
ZIndex = 2;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1);
}
local thumbLoader = ThumbnailLoader:Create(image, data.IconId, thumbSize, assetType)
spawn(function()
if not thumbLoader:LoadAsync() then
-- TODO
end
end)
table.insert(images, image)
carousel:AddItem(image)
-- connect button press based on current selected index mapped to gameData
image.MouseButton1Click:connect(function()
if lastSelectedIndex and gameData[lastSelectedIndex] then
local currentData = gameData[lastSelectedIndex]
EventHub:dispatchEvent(EventHub.Notifications["OpenGameDetail"], currentData.PlaceId,
currentData.Name, currentData.IconId, currentData.GameData)
end
end)
end
end
end
--[[ Event Connections ]]--
local nextLoadIndex = 0
local lastSelectedObject = nil
local onNewFocusCn = nil
local function onNewFocusItem(newIndex, isRight, selectedObject)
lastSelectedObject = selectedObject
lastSelectedIndex = newIndex
--
if gameData[newIndex] then
local currentData = gameData[newIndex]
if this then
this.OnNewGameSelected:fire(currentData)
end
-- description/isFavorited needs to fetch, so send a late update
spawn(function()
if not currentData.GameData then
local gameData = GameDataModule:GetGameDataAsync(currentData.PlaceId)
if gameData then
currentData.GameData = gameData
currentData.Description = gameData:GetDescription()
currentData.IsFavorited = gameData:GetIsFavoritedByUser()
end
end
if this and selectedObject == lastSelectedObject then
this.OnNewGameSelectedLate:fire(currentData.Description, currentData.IsFavorited)
end
end)
end
-- load more data
if isRight and #gameData > IMAGE_COUNT and newIndex == nextLoadIndex then
nextLoadIndex = nextLoadIndex + LOAD_COUNT
local page = gameCollection:GetSortAsync(lastLoadedCount, LOAD_COUNT)
insertSortData(page)
carousel:SetMaxItems(#gameData)
end
end
onNewFocusCn = carousel.OnNewFocusItem:connect(onNewFocusItem)
--[[ Public API ]]--
function this:SetParent(newParent)
carousel:SetParent(newParent)
end
function this:GetCurrentSelectedGameData()
if gameData[lastSelectedIndex] then
return gameData[lastSelectedIndex]
end
end
function this:GetItemAt(index)
return images[index]
end
function this:Destroy()
carousel:Destroy()
if onNewFocusCn then
onNewFocusCn:disconnect()
onNewFocusCn = nil
end
spawn(function()
gameData = nil
this = nil
end)
end
function this:LoadSortAsync()
local loader = LoadingWidget(
{ Parent = parent },{
function()
local page = gameCollection:GetSortAsync(0, LOAD_COUNT)
insertSortData(page)
createCarouselImages()
if carousel then
carousel:SetDataTable(gameData)
carousel:SetMaxItems(#gameData)
carousel:SetFocusPosition(1)
onNewFocusItem(1, nil, images[1])
nextLoadIndex = LOAD_COUNT / 2
end
end
})
loader:AwaitFinished()
loader:Cleanup()
loader = nil
end
return this
end
return createSortCarousel
@@ -0,0 +1,230 @@
--[[
// SortData.lua
// API for Game Sorts
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GameData = require(Modules:FindFirstChild('GameData'))
local Http = require(Modules:FindFirstChild('Http'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local SortData = {}
-- this is a temp cache for play my place. We need to be able to check if a user is joining
-- a featured game or not in order to set their session privacy.
local featuredSortCache = nil
local FEATURED_SORT_ID = 3
local REFRESH_TIME = 300
local lastTimeUpdated = 0
local isFetching = false
local function getFeaturedCacheAsync()
while isFetching do
wait()
end
if featuredSortCache and (tick() - lastTimeUpdated < REFRESH_TIME) then
return featuredSortCache
end
isFetching = true
local newFeaturedSortCache = {}
local lastIndexLoaded = 0
local PAGE_SIZE = 100
local sort = SortData.GetSort(FEATURED_SORT_ID)
local getFeaturedSortSuccess = true
local isLastPage = false
repeat
local currentPage = nil
local function tryGetSortAsync()
currentPage = sort:GetPageAsync(lastIndexLoaded, PAGE_SIZE)
if currentPage then
return true
end
end
Utility.ExponentialRepeat(
function() return currentPage == nil end, tryGetSortAsync, 3)
if currentPage then
if currentPage.Count > 0 then
local placeIds = currentPage:GetPagePlaceIds()
for i = 1, #placeIds do
newFeaturedSortCache[placeIds[i]] = true
end
lastIndexLoaded = lastIndexLoaded + currentPage.Count
end
isLastPage = currentPage.Count < PAGE_SIZE
else
getFeaturedSortSuccess = false
end
until not getFeaturedSortSuccess == false or isLastPage == true
if getFeaturedSortSuccess == true then
featuredSortCache = newFeaturedSortCache
end
lastTimeUpdated = tick()
isFetching = false
return featuredSortCache
end
function SortData:IsFeaturedGameAsync(placeId)
local featuredSort = getFeaturedCacheAsync()
if featuredSort then
return featuredSort[placeId]
end
return false
end
local function initPage(data)
local page = {}
page.Data = data
page.Count = #data
function page:GetPagePlaceIds()
local ids = {}
for i = 1, #data do
ids[i] = data[i]["PlaceID"]
end
return ids
end
function page:GetPagePlaceNames()
local names = {}
for i = 1, #data do
names[i] = GameData:GetFilteredGameName(data[i]["Name"], data[i]["CreatorName"])
end
return names
end
function page:GetCreatorNames()
local names = {}
for i = 1, #data do
names[i] = data[i]["CreatorName"]
end
return names
end
function page:GetPageIconIds()
local iconIds = {}
for i = 1, #data do
local id = data[i]["ImageId"]
iconIds[i] = id
end
return iconIds
end
function page:GetPageVoteData()
local voteData = {}
for i = 1, #data do
local vote = {
UpVotes = data[i]["TotalUpVotes"];
DownVotes = data[i]["TotalDownVotes"];
}
voteData[i] = vote
end
return voteData
end
function page:GetPageCreatorUserIds()
local creatorIds = {}
for i = 1, #data do
local id = data[i]["CreatorID"]
table.insert(creatorIds, id)
end
return creatorIds
end
return page
end
-- returns class style table for sorts
function SortData.GetSortCategoriesAsync()
local result = Http.GetGameSortsAsync()
if not result then
-- TODO: Error codes
return
end
--
local sorts = {}
for i = 1, #result do
local sort = {
Id = result[i]["Id"];
Name = result[i]["Name"];
}
table.insert(sorts, sort)
end
return sorts
end
local function createSort(sort, sortId, httpFunc)
function sort:GetPageAsync(startIndex, pageSize, timeFilter)
local result = httpFunc(startIndex, pageSize, sortId, timeFilter)
if not result then
return nil
end
return initPage(result)
end
end
function SortData.GetSort(sortId)
local sort = {}
local httpFunc = Http.GetSortAsync
createSort(sort, sortId, httpFunc)
return sort
end
function SortData.GetUserFavorites()
local sort = {}
local sortId = "MyFavorite"
local httpFunc = Http.GetUserFavoritesAsync
createSort(sort, sortId, httpFunc)
return sort
end
function SortData.GetUserRecent()
local sort = {}
local sortId = "MyRecent"
local httpFunc = Http.GetUserRecentAsync
createSort(sort, sortId, httpFunc)
return sort
end
function SortData.GetUserPlaces(userId)
local sort = {}
local httpFunc = Http.GetUserPlacesAsync
createSort(sort, userId, httpFunc)
return sort
end
return SortData
@@ -0,0 +1,206 @@
-- local CoreGui = Game:GetService("CoreGui")
-- local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local GuiService = game:GetService('GuiService')
local SoundService = Game:GetService("SoundService")
local runService = game:GetService("RunService")
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local BASE_SOUND_URL = 'rbxasset://sounds/ui/Shell/'
local SOUNDS =
{
--[[ BGM ]]--
['BackgroundLoop'] = 'RobloxMusic.ogg';
--[[ UI Sounds ]]--
['Error'] = 'Error.mp3';
['ButtonPress'] = 'ButtonPress.mp3';
['MoveSelection'] = 'MoveSelection.mp3';
['OverlayOpen'] = 'OverlayOpen.mp3';
['PopUp'] = 'PopUp.mp3';
['PurchaseSuccess'] = 'PurchaseSuccess.mp3';
['ScreenChange'] = 'ScreenChange.mp3';
['SideMenuSlideIn'] = 'SideMenuSlideIn.mp3';
}
local SoundQueue = {}
local function EaseOutCirc(currentTime, startValue, deltaValue, duration)
currentTime = currentTime / duration;
currentTime = currentTime - 1;
return deltaValue * math.sqrt(1 - currentTime*currentTime) + startValue;
end
local function IsGameRunning()
if not UserSettings().GameSettings:InStudioMode() then
return true
end
return runService:IsRunning()
end
GetSoundManager = function()
local this = {}
local rawVolumes = {}
local function FindSoundObjectForName(soundName)
-- local soundsFolder = this.SoundHolder and this.SoundHolder:FindFirstChild(soundName)
-- local soundObj = nil
-- for _, otherSoundObj in pairs(soundsFolder:GetChildren()) do
-- print("Other:" , otherSoundObj , " is time" , otherSoundObj.TimePosition, "tl:" , otherSoundObj.TimeLength)
-- if not otherSoundObj.IsPlaying then
-- if soundObj then
-- otherSoundObj:Destroy()
-- else
-- soundObj = otherSoundObj
-- end
-- end
-- end
-- return soundObj
if SoundQueue[soundName] then
local soundObj = table.remove(SoundQueue[soundName], 1)
if soundObj then
table.insert(SoundQueue[soundName], #SoundQueue[soundName], soundObj)
return soundObj
end
end
end
function this:CreateSound(soundName)
local fileName = SOUNDS[soundName]
local soundsUrl = BASE_SOUND_URL .. fileName
local soundObj = Instance.new('Sound')
soundObj.Name = soundName
soundObj.SoundId = soundsUrl
return soundObj
end
function this:Play(soundName, vol, isLoop, pitch, ...)
local result = nil
if SOUNDS[soundName] then
local soundObj = FindSoundObjectForName(soundName)
if soundObj then
soundObj.Volume = vol or 1
soundObj.Looped = isLoop or false
soundObj.Pitch = pitch or 1
soundObj:Play(...)
rawVolumes[soundObj] = soundObj.Volume
if not IsGameRunning() then
soundObj.Volume = 0
end
result = soundObj
else
print("No sound:" , soundName , "in the queue.")
end
else
spawn(function()
error("Unable to find sound: " .. tostring(soundName))
end)
end
return result
end
function this:IsPlaying(soundName)
local sound = this.SoundHolder:FindFirstChild(soundName)
if sound then
return sound.IsPlaying
end
return false
end
function this:Stop(soundName, ...)
if not Player then return end
if this.SoundHolder and SOUNDS[soundName] then
local soundObj = this.SoundHolder:FindFirstChild(soundName)
if soundObj then
soundObj:Stop()
end
end
end
function this:TweenSound(soundObj, newVolume, duration)
rawVolumes[soundObj] = nil
Utility.PropertyTweener(soundObj, 'Volume', soundObj.Volume, newVolume, duration,
function(...)
return IsGameRunning() and Utility.EaseInOutQuad(...) or 0
end,
true,
function()
rawVolumes[soundObj] = newVolume
end)
end
-- function this:PlayOnEvent(event, soundName)
-- event:connect(function()
-- end)
-- end
local function Initialize()
local appshellSounds = Instance.new('Folder')
appshellSounds.Name = 'AppShellSounds'
appshellSounds.Parent = SoundService
this.SoundHolder = appshellSounds
for name, fileName in pairs(SOUNDS) do
local soundsForFile = Instance.new('Folder')
soundsForFile.Name = name
soundsForFile.Parent = this.SoundHolder
SoundQueue[name] = {}
for i = 1, 3 do
local soundObj = this:CreateSound(name)
soundObj.Parent = soundsForFile
table.insert(SoundQueue[name], soundObj)
end
end
local lastSelection = nil
GuiService.Changed:connect(function(property)
if property == 'SelectedCoreObject' then
local currentSelection = GuiService.SelectedCoreObject
if currentSelection and lastSelection then
local moveSelectionSound = currentSelection:FindFirstChild('MoveSelection')
if moveSelectionSound and moveSelectionSound:IsA('Sound') then
moveSelectionSound:Play()
end
end
lastSelection = currentSelection
end
end)
if not IsGameRunning() then
spawn(function()
while not IsGameRunning() do
wait(0.1)
end
for soundObj, rawVolume in pairs(rawVolumes) do
soundObj.Volume = rawVolume
end
end)
end
end
Initialize()
return this
end
return GetSoundManager()
@@ -0,0 +1,576 @@
--[[
// StorePane.lua by Kip Turner
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local ScrollingGridModule = require(Modules:FindFirstChild('ScrollingGrid'))
local UserDataModule = require(Modules:FindFirstChild('UserData'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local CreateConfirmPrompt = require(Modules:FindFirstChild('ConfirmPrompt'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local PlatformCatalogData = require(Modules:FindFirstChild('PlatformCatalogData'))
local CurrencyWidgetModule = require(Modules:FindFirstChild('CurrencyWidget'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local RobuxBalanceOverlay = require(Modules:FindFirstChild('RobuxBalanceOverlay'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local Errors = require(Modules:FindFirstChild('Errors'))
local TextService = game:GetService('TextService')
local GuiService = game:GetService('GuiService')
local PlatformService;
pcall(function() PlatformService = game:GetService('PlatformService') end)
--[[ Constants ]]--
local GRID_ROWS = 2
local GRID_COLUMNS = 3
local GRID_SPACING = Vector2.new(20,20)
local GRID_SIZE = UDim2.new(0, 1620, 0, 610)
local GRID_CELL_SIZE = Vector2.new(520,285)
local GRID_POSITION = UDim2.new(0, 40, 0, 65)
local DESCRIPTION_SIZE = UDim2.new(1,0,0,50)
local PRICE_CORNER_OFFSET = Vector2.new(-15,-12)
local NO_ITEMS_MSG_POSITION = UDim2.new(0.1, 0, 0, 275)
local NO_ITEMS_MSG_SIZE = UDim2.new(0.8, 0, 0, 150)
local ROBUX_ASSETS =
{
{
Wide = 'Robux01.png';
Square = 'RobuxSquare01.png';
};
{
Wide = 'Robux02.png';
Square = 'RobuxSquare02.png';
};
{
Wide = 'Robux03.png';
Square = 'RobuxSquare03.png';
};
{
Wide = 'Robux04.png';
Square = 'RobuxSquare04.png';
};
{
Wide = 'Robux05.png';
Square = 'RobuxSquare05.png';
};
{
Wide = 'Robux06.png';
Square = 'RobuxSquare06.png';
};
}
local function createGridItem(productInfo)
local this = {}
local container = Utility.Create'ImageButton'
{
Name = "StoreItemContainer",
BackgroundTransparency = 1,
BackgroundColor3 = Color3.new(220/255, 220/255, 220/255),
-- Image = '',
AutoButtonColor = false,
BorderSizePixel = 0,
ZIndex = 2;
AssetManager.CreateShadow(1);
SoundManager:CreateSound('MoveSelection');
}
local priceText = Utility.Create'TextLabel'
{
Name = "PriceText",
Size = UDim2.new(0, 0, 0, 0),
Position = UDim2.new(1, -15, 1, -12),
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Right,
TextYAlignment = 'Bottom';
TextColor3 = GlobalSettings.WhiteTextColor,
Font = GlobalSettings.HeadingFont,
FontSize = GlobalSettings.LargeFontSize,
Text = '',
ZIndex = 2;
Parent = container,
}
local dollarSign = Utility.Create'TextLabel'
{
Name = "DollarSign",
Size = UDim2.new(0, 0, 1, -15),
Position = UDim2.new(0, 0, 0, 15),
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Right,
TextYAlignment = Enum.TextYAlignment.Top,
TextColor3 = GlobalSettings.WhiteTextColor,
Font = GlobalSettings.BoldFont,
FontSize = GlobalSettings.MediumFontSize,
Text = Strings:LocalizedString('CurrencySymbol'),
ZIndex = 2;
Parent = priceText,
}
local robuxIcon = Utility.Create'ImageLabel'
{
Name = 'RobuxIcon';
Position = UDim2.new(0,5,0,5);
Size = UDim2.new(0,80,0,80);
Image = 'rbxasset://textures/ui/Shell/Icons/ROBUXIcon@1080.png';
BackgroundTransparency = 1;
ZIndex = 2;
Parent = container;
};
local robuxAmount = Utility.Create'TextLabel'
{
Name = 'RobuxAmount';
Text = '';
Size = UDim2.new(0,0,1,0);
Position = UDim2.new(1,10,0,0);
TextXAlignment = 'Left';
TextColor3 = GlobalSettings.WhiteTextColor;
Font = GlobalSettings.BoldFont;
FontSize = GlobalSettings.LargeFontSize;
BackgroundTransparency = 1;
ZIndex = 2;
Parent = robuxIcon;
};
local percentMoreText = Utility.Create'TextLabel'
{
Name = "PercentMoreText",
Size = UDim2.new(0, 0, 0, 0),
Position = UDim2.new(0, 5, 1, 10),
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextColor3 = GlobalSettings.GreenTextColor,
Font = GlobalSettings.BoldFont,
FontSize = GlobalSettings.ButtonSize,
-- Visible = false,
Text = '',
ZIndex = 2;
Parent = robuxIcon,
}
local function UpdateInfo()
local priceTextSize = TextService:GetTextSize(priceText.Text, Utility.ConvertFontSizeEnumToInt(priceText.FontSize), priceText.Font, Vector2.new())
priceText.Size = UDim2.new(0, priceTextSize.x , 0, priceTextSize.y)
priceText.Position = UDim2.new(1, PRICE_CORNER_OFFSET.x - priceTextSize.x, 1, PRICE_CORNER_OFFSET.y - priceTextSize.y)
end
UpdateInfo()
function this:GetContainer()
return container
end
function this:SetDollarPrice(value)
priceText.Text = value
UpdateInfo()
end
function this:SetRobuxValue(value)
robuxAmount.Text = Utility.FormatNumberString(tostring(value))
end
function this:SetPercentMore(value)
percentMoreText.Visible = value > 0
percentMoreText.Text = string.format(Strings:LocalizedString('PercentMoreRobuxPhrase'), tostring(value))
end
function this:SetImage(image)
container.Image = image
end
return this
end
local function CreateStorePane(parent)
local this = {}
local storeItemClickConns = {}
local cachedBalance = nil
local cachedTotalBalance = nil
local inFocus = false
local currencyWidget = nil
local StorePaneContainer = Utility.Create'Frame'
{
Name = 'StorePane',
Size = UDim2.new(1, 0, 1, 0),
BackgroundTransparency = 1,
Visible = false,
Parent = parent,
}
local StoreDescriptionText = Utility.Create'TextLabel'
{
Name = "StoreDescriptionText",
Size = DESCRIPTION_SIZE,
Position = UDim2.new(0, 0, 0, 0),
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Left,
TextColor3 = GlobalSettings.WhiteTextColor,
Font = GlobalSettings.LightFont,
FontSize = GlobalSettings.TitleSize,
TextWrapped = true,
Visible = false;
Text = Strings:LocalizedString('RobuxStoreDescription'),
Parent = StorePaneContainer,
}
local StoreNoItemsText = Utility.Create'TextLabel'
{
Name = "StoreNoItemsText",
Size = NO_ITEMS_MSG_SIZE,
Position = NO_ITEMS_MSG_POSITION,
BackgroundTransparency = 1,
TextXAlignment = Enum.TextXAlignment.Center,
FontSize = GlobalSettings.TitleSize,
TextWrapped = true,
TextColor3 = GlobalSettings.GreyTextColor;
TextTransparency = GlobalSettings.FriendStatusTextTransparency;
Font = GlobalSettings.BoldFont;
Text = Strings:LocalizedString('RobuxStoreNoItemsPhrase'),
Visible = false;
Parent = StorePaneContainer,
}
local RobuxBalanceButton = Utility.Create'ImageButton'
{
Name = "RobuxBalanceButton";
-- size will change based on text bounds of balance
Size = UDim2.new(0, 436, 0, 75);
Position = UDim2.new(0, 0, 1, -100);
BackgroundTransparency = 1;
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.GreySelectionColor;
Selectable = false;
Parent = StorePaneContainer;
SoundManager:CreateSound('MoveSelection')
};
RobuxBalanceButton.SelectionGained:connect(function()
RobuxBalanceButton.BackgroundTransparency = 0;
end)
RobuxBalanceButton.SelectionLost:connect(function()
RobuxBalanceButton.BackgroundTransparency = 1;
end)
local RobuxHelpIcon = Utility.Create'ImageLabel'
{
Name = "RobuxHelpIcon";
Size = UDim2.new(0, 42, 0, 42);
Position = UDim2.new(0, 10, 0, 0);
BackgroundTransparency = 1;
Image = 'rbxasset://textures/ui/Shell/Icons/HelpIconSmall.png';
Visible = false;
Parent = RobuxBalanceButton;
};
Utility.CalculateAnchor(RobuxHelpIcon, UDim2.new(0, 10, 0.5, 4), Utility.Enum.Anchor.CenterLeft)
local function setBalanceButtonSize(balanceObjectSize)
local sizeX = 56 + RobuxHelpIcon.Size.X.Offset + (balanceObjectSize and balanceObjectSize.X or 0) + 10
RobuxBalanceButton.Size = UDim2.new(0, sizeX, 0, 75)
end
local showBalanceHelp = true
local showBalanceOverlayDebounce = false
RobuxBalanceButton.MouseButton1Click:connect(function()
if showBalanceOverlayDebounce then return end
--
showBalanceOverlayDebounce = true
if showBalanceHelp then
local robuxBalanceOverlay = RobuxBalanceOverlay(cachedBalance, cachedTotalBalance)
ScreenManager:OpenScreen(robuxBalanceOverlay, false)
end
showBalanceOverlayDebounce = false
end)
local function setBalanceHelpOption(platformBalance)
local totalBalance = UserDataModule.GetTotalUserBalanceAsync()
if totalBalance then
cachedTotalBalance = totalBalance
showBalanceHelp = platformBalance ~= totalBalance
RobuxHelpIcon.Visible = showBalanceHelp
RobuxBalanceButton.Selectable = showBalanceHelp
end
end
local function PopulateBalance()
spawn(function()
local platformBalance = currencyWidget and currencyWidget:GetRobuxAmountAsync() or UserDataModule.GetPlatformUserBalanceAsync()
if platformBalance then
cachedBalance = platformBalance
setBalanceHelpOption(platformBalance)
if currencyWidget then
setBalanceButtonSize(currencyWidget:GetAbsoluteSize())
end
else
print("Unable to update user's balance because web call failed.")
end
end)
end
local storeScrollingGrid = ScrollingGridModule()
storeScrollingGrid:SetSize(GRID_SIZE)
storeScrollingGrid:SetScrollDirection(storeScrollingGrid.Enum.ScrollDirection.Vertical)
storeScrollingGrid:SetCellSize(GRID_CELL_SIZE)
storeScrollingGrid:SetPosition(GRID_POSITION)
storeScrollingGrid:SetRowColumnConstraint(GRID_COLUMNS)
storeScrollingGrid:SetSpacing(GRID_SPACING)
storeScrollingGrid:SetParent(StorePaneContainer)
--
local SuccessfullyLoadedCatalog = false
local catalogLoading = false
local function OnLoad()
if catalogLoading or SuccessfullyLoadedCatalog then return end
catalogLoading = true
if PlatformService then
local catalogInfo, success, errormsg;
-- Hide these text labels while we are loading
StoreDescriptionText.Visible = false
StoreNoItemsText.Visible = false
local loader = LoadingWidget({Parent = StorePaneContainer}, {function() catalogInfo, success, errormsg = PlatformCatalogData:GetCatalogInfoAsync() end})
loader:AwaitFinished()
loader:Cleanup()
loader = nil
ScreenManager:DefaultFadeIn(storeScrollingGrid:GetGuiObject())
if inFocus and GuiService.SelectedCoreObject == nil then
if storeScrollingGrid.GridItems[1] then
GuiService.SelectedCoreObject = storeScrollingGrid.GridItems[1]
end
end
if success and catalogInfo then
while #storeItemClickConns > 0 do
Utility.DisconnectEvent(table.remove(storeItemClickConns, 1))
end
table.sort(catalogInfo, function(a, b)
local aPrice = PlatformCatalogData:ParseRobuxValue(a)
local bPrice = PlatformCatalogData:ParseRobuxValue(b)
if aPrice and bPrice then
return aPrice < bPrice
end
return a < b
end)
local worstRatio = nil
for _, productInfo in pairs(catalogInfo) do
local ratio = PlatformCatalogData:CalculateRobuxRatio(productInfo)
if Utility.IsFinite(ratio) and ratio ~= 0 then
if worstRatio == nil or ratio < worstRatio then
worstRatio = ratio
end
end
end
if #catalogInfo == 0 then
StoreDescriptionText.Visible = false
StoreNoItemsText.Visible = true
elseif not SuccessfullyLoadedCatalog then
SuccessfullyLoadedCatalog = true
StoreDescriptionText.Visible = true
StoreNoItemsText.Visible = false
local i = 1
for _, productInfo in pairs(catalogInfo) do
local productImageData = ROBUX_ASSETS[math.min(i, #ROBUX_ASSETS)]
local catalogItemImage = 'rbxasset://textures/ui/Shell/Images/Robux/' .. productImageData['Wide']
local confirmItemImage = 'rbxasset://textures/ui/Shell/Images/Robux/' .. productImageData['Square']
local debounce = false
local function onClick()
if debounce then return end
debounce = true
local confirmPrompt = CreateConfirmPrompt({ProductName = productInfo and productInfo.Name or 'Unknown'; Balance = cachedBalance; Cost = productInfo and productInfo.DisplayListPrice or "Unknown"; ProductImage = confirmItemImage; ProductImageSize = Vector2.new(484, 540); CurrencySymbol = '';},
{ShowRemainingBalance = false; ShowRobuxIcon = false; ConfirmWithPrice = true;})
do
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(StorePaneContainer) then
this.SavedSelection = selectedObject
end
end
confirmPrompt:SetParent(GuiRoot)
ScreenManager:OpenScreen(confirmPrompt)
confirmPrompt:FadeInBackground()
local function onConfirmFinished(result)
if result == true then
print("Do buy")
local purchaseResult = nil
if not UserSettings().GameSettings:InStudioMode() then
local purchaseCallSuccess, purchaseErrorMsg = pcall(function()
purchaseResult = PlatformService:BeginPlatformStorePurchase(productInfo.ProductId)
end)
if purchaseCallSuccess then
-- 0 means we bought it
-- print("purchaseResult" , purchaseResult)
if purchaseResult == 0 then
-- print("dispatchEvent RobuxCatalogPurchaseInitiated")
EventHub:dispatchEvent(EventHub.Notifications["RobuxCatalogPurchaseInitiated"], purchaseResult);
end
else
print("Purchase Robux failed with pcall status:" , purchaseCallSuccess , "and purchaseResult:" , purchaseResult , "because of:" , purchaseErrorMsg)
end
else
spawn(function()
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.RobuxPurchase[1]), false)
end)
end
PopulateBalance()
print("Done with purchase; result was:" , purchaseResult)
else
print("Declined to buy")
end
end
-- confirmPrompt:AddResultCallback(onConfirmFinished)
local result = confirmPrompt:ResultAsync()
onConfirmFinished(result)
debounce = false
end
local extractedPrice = productInfo and productInfo.DisplayPrice and string.gsub(productInfo.DisplayPrice, "%$", "") or ""
-- local extractedRobuxValue = productInfo and productInfo.Name and string.match(productInfo.Name, "[0-9,]+") or "1000"
local thisRatio = PlatformCatalogData:CalculateRobuxRatio(productInfo)
local item = createGridItem()
item:SetDollarPrice(extractedPrice)
item:SetRobuxValue(PlatformCatalogData:ParseRobuxValue(productInfo))
if thisRatio and worstRatio then
item:SetPercentMore(math.floor(((thisRatio / worstRatio) - 1) * 100))
else
item:SetPercentMore(0)
end
item:SetImage(catalogItemImage)
storeScrollingGrid:AddItem(item:GetContainer())
table.insert(storeItemClickConns, item:GetContainer().MouseButton1Click:connect(onClick))
i = math.min(#ROBUX_ASSETS, i + 1)
end
end
else
StoreNoItemsText.Visible = true
print("StorePane - BeginGetCatalogInfo failed because:" , errormsg)
end
end
catalogLoading = false
end
if not SuccessfullyLoadedCatalog then
spawn(OnLoad)
end
--[[ Public API ]]--
function this:GetName()
return Strings:LocalizedString('CatalogWord')
end
function this:IsFocused()
return inFocus
end
local RobuxChangedConn = nil
local robuxChangedEventCount = 0
local robuxAmountChangedLoader = nil
function this:Show()
StorePaneContainer.Visible = true
if not currencyWidget then
currencyWidget = CurrencyWidgetModule({Parent = RobuxBalanceButton; Position = UDim2.new(0, RobuxHelpIcon.Position.X.Offset + RobuxHelpIcon.Size.X.Offset + 10, 0.5, -30);})
else
spawn(function()
currencyWidget:RefreshRobuxAmountAsync()
end)
end
setBalanceButtonSize(currencyWidget:GetAbsoluteSize())
Utility.DisconnectEvent(RobuxChangedConn)
RobuxChangedConn = currencyWidget.RobuxChanged:connect(function()
PopulateBalance()
currencyWidget:GetRobuxAmountAsync()
setBalanceButtonSize(currencyWidget:GetAbsoluteSize())
SoundManager:Play('PurchaseSuccess')
end)
PopulateBalance()
self.TransitionTweens = ScreenManager:DefaultFadeIn(StorePaneContainer)
ScreenManager:PlayDefaultOpenSound()
if not SuccessfullyLoadedCatalog then
spawn(OnLoad)
end
end
function this:Hide()
StorePaneContainer.Visible = false
RobuxChangedConn = Utility.DisconnectEvent(RobuxChangedConn)
ScreenManager:DefaultCancelFade(self.TransitionTweens)
self.TransitionTweens = nil
-- Let's not do this it creates weird race conditions
-- if currencyWidget then
-- currencyWidget:Destroy()
-- currencyWidget = nil
-- end
end
function this:Focus()
-- TODO: What is the selection if the packages fail to load?
inFocus = true
if self.SavedSelection and self.SavedSelection:IsDescendantOf(StorePaneContainer) then
GuiService.SelectedCoreObject = self.SavedSelection
elseif storeScrollingGrid.GridItems[1] then
GuiService.SelectedCoreObject = storeScrollingGrid.GridItems[1]
end
self.SavedSelection = nil
end
function this:RemoveFocus()
inFocus = false
local selectedObject = GuiService.SelectedCoreObject
if selectedObject and selectedObject:IsDescendantOf(StorePaneContainer) then
GuiService.SelectedCoreObject = nil
end
end
function this:SetPosition(newPosition)
StorePaneContainer.Position = newPosition
end
function this:SetParent(newParent)
StorePaneContainer.Parent = newParent
end
function this:IsAncestorOf(object)
return StorePaneContainer and StorePaneContainer:IsAncestorOf(object)
end
return this
end
return CreateStorePane
@@ -0,0 +1,282 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local function CreateTabDock()
local this = {}
local Tabs = {}
local SelectedTab = nil
local SizeChangedConns = {}
this.SelectedTabChanged = Utility.Signal()
this.SelectedTabClicked = Utility.Signal()
local guiServiceChangedCn = nil
local TabContainer = Utility.Create'ImageButton'
{
Size = UDim2.new(1, 0, 0, 36);
BackgroundTransparency = 1;
Name = 'TabContainer';
}
local SelectionBorderObject = Utility.Create'ImageLabel'
{
Name = 'SelectionBorderObject';
Size = UDim2.new(1,0,0,4);
Position = UDim2.new(0,0,1,5);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.TabUnderlineColor;
-- Image = 'rbxasset://textures/ui/SelectionBox.png';
-- ScaleType = Enum.ScaleType.Slice;
-- SliceCenter = Rect.new(19,19,43,43);
BackgroundTransparency = 0;
};
local DownSelector = Utility.Create'ImageButton'
{
Size = UDim2.new(1, 0, 0, 36);
BackgroundTransparency = 1;
Name = 'DownSelector';
Selectable = false;
Parent = TabContainer;
}
DownSelector.SelectionGained:connect(function()
if SelectedTab then
GuiService.SelectedCoreObject = SelectedTab:GetGuiObject()
this.SelectedTabClicked:fire(SelectedTab)
end
end)
local function onGuiServiceChanged(prop)
if prop == 'SelectedCoreObject' then
if GuiService.SelectedCoreObject == TabContainer then
local currentTab = this:GetSelectedTab()
local currentTabItem = currentTab and currentTab:GetGuiObject()
if currentTabItem then
GuiService.SelectedCoreObject = currentTabItem
end
end
local selectedObject = GuiService.SelectedCoreObject
local focusedTab = this:FindFocusedTabByGuiObject(selectedObject)
if SelectedTab and selectedObject ~= SelectedTab:GetGuiObject() then
SelectedTab:OnClickRelease()
end
if focusedTab then
this:SetSelectedTab(focusedTab)
for _, inputObject in pairs(UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)) do
if inputObject.KeyCode == Enum.KeyCode.ButtonA and inputObject.UserInputState == Enum.UserInputState.Begin then
if SelectedTab then
SelectedTab:OnClick()
end
end
end
ContextActionService:UnbindCoreAction("OnClickSelectedTab")
ContextActionService:BindCoreAction("OnClickSelectedTab",
function(actionName, inputState, inputObject)
if inputState == Enum.UserInputState.Begin then
if SelectedTab then
SelectedTab:OnClick()
end
elseif inputState == Enum.UserInputState.End then
if SelectedTab then
SelectedTab:OnClickRelease()
end
this.SelectedTabClicked:fire(SelectedTab)
end
end,
false,
Enum.KeyCode.ButtonA)
else
ContextActionService:UnbindCoreAction("OnClickSelectedTab")
end
end
end
function this:FindFocusedTabByGuiObject(selectedObject)
-- NOTE: This is a sort of cheater way of culling look-up checks
if selectedObject and selectedObject:IsDescendantOf(TabContainer) then
for _, currTab in pairs(Tabs) do
local guiObject = currTab and currTab:GetGuiObject()
if guiObject and guiObject == selectedObject then
return currTab
end
end
end
end
function this:IsFocused()
local selectedObject = GuiService.SelectedCoreObject
return self:FindFocusedTabByGuiObject(selectedObject) ~= nil
end
function this:SetSelectedTab(newSelectedTab)
if newSelectedTab ~= SelectedTab then
if SelectedTab then
SelectedTab:SetSelected(false)
SelectedTab:OnClickRelease()
end
SelectedTab = newSelectedTab
if SelectedTab then
SelectedTab:SetSelected(true)
if self:IsFocused() then
local currentTabItem = SelectedTab and SelectedTab:GetGuiObject()
if currentTabItem then
GuiService.SelectedCoreObject = currentTabItem
end
end
end
-- Fire tab selection event with the name of the new tab selection
this.SelectedTabChanged:fire(SelectedTab)
end
end
function this:Focus()
if SelectedTab then
SelectedTab:SetSelected(true)
local currentTabItem = SelectedTab and SelectedTab:GetGuiObject()
if currentTabItem then
GuiService.SelectedCoreObject = currentTabItem
end
end
end
function this:GetSelectedTab()
return SelectedTab
end
local arrangeCount = 0
function this:ArrangeTabs()
arrangeCount = arrangeCount + 1
local currentCount = arrangeCount
local x = 0
for i, tabItem in pairs(Tabs) do
local tabItemSize = tabItem:GetSize()
local xSize = tabItemSize.X.Offset
local spacing = GlobalSettings.TabItemSpacing
if i == 1 then
spacing = 0
end
-- Stop recursion in its tracks
if currentCount == arrangeCount then
tabItem:SetPosition(UDim2.new(0, x + spacing, 0, 0))
local tabItemGuiObject = tabItem:GetGuiObject()
if tabItemGuiObject then
local prevItemGuiObject = Tabs[i-1] and Tabs[i-1]:GetGuiObject()
local nextItemGuiObject = Tabs[i+1] and Tabs[i+1]:GetGuiObject()
tabItemGuiObject.NextSelectionLeft = prevItemGuiObject
tabItemGuiObject.NextSelectionRight = nextItemGuiObject
end
else
return
end
x = x + spacing + xSize
end
end
function this:FindTabIndex(tab)
for i, currTab in pairs(Tabs) do
if tab == currTab then
return i
end
end
end
function this:GetNextTab()
if SelectedTab then
local index = this:FindTabIndex(SelectedTab)
return index and Tabs[index + 1]
end
end
function this:GetPreviousTab()
if SelectedTab then
local index = this:FindTabIndex(SelectedTab)
return index and Tabs[index - 1]
end
end
function this:AddTab(newTab)
local existingIndex = self:FindTabIndex(newTab)
if existingIndex then
print("Not adding tab:" , newTab:GetName() , "because that tab already exists.")
return
end
local guiObject = newTab and newTab:GetGuiObject()
if guiObject then
guiObject.SelectionImageObject = SelectionBorderObject
guiObject.NextSelectionDown = DownSelector
end
table.insert(Tabs, newTab)
newTab:SetParent(TabContainer)
Utility.DisconnectEvent(SizeChangedConns[newTab])
SizeChangedConns[newTab] = newTab.SizeChanged:connect(function()
self:ArrangeTabs()
end)
this:ArrangeTabs()
return newTab
end
function this:RemoveTab(tab)
local removeIndex = self:FindTabIndex(tab)
if removeIndex then
table.remove(Tabs, removeIndex)
if tab == SelectedTab then
this:SetSelectedTab(nil)
end
Utility.DisconnectEvent(SizeChangedConns[tab])
return true
end
return false
end
function this:SetPosition(newPosition)
TabContainer.Position = newPosition
end
function this:SetParent(newParent)
TabContainer.Parent = newParent
end
function this:ConnectEvents()
onGuiServiceChanged('SelectedCoreObject')
guiServiceChangedCn = GuiService.Changed:connect(onGuiServiceChanged)
end
function this:DisconnectEvents()
if guiServiceChangedCn then
guiServiceChangedCn:disconnect()
guiServiceChangedCn = nil
end
end
return this
end
return CreateTabDock
@@ -0,0 +1,111 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local TextService = game:GetService('TextService')
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Utility = require(Modules:FindFirstChild('Utility'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local function CreateTabDockItem(tabName, contentItem)
local this = {}
local name = tabName
local selected = false
local content = contentItem
this.SizeChanged = Utility.Signal()
local tabItem = Utility.Create'TextLabel'
{
Text = name;
Size = UDim2.new(0, 100, 1, 0);
BackgroundTransparency = 1;
Name = 'TabItem';
FontSize = GlobalSettings.HeaderSize;
Font = GlobalSettings.LightFont;
BackgroundTransparency = 1;
}
do
local tabItemTextSize = TextService:GetTextSize(tabItem.Text, Utility.ConvertFontSizeEnumToInt(tabItem.FontSize), tabItem.Font, Vector2.new())
tabItem.Size = UDim2.new(0,tabItemTextSize.X,1,0)
this.SizeChanged:fire(tabItem.Size)
end
local smallText = Utility.Create'TextLabel'
{
Text = name;
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0.5, 0, 0.5, 0);
BackgroundTransparency = 1;
Name = 'SmallText';
FontSize = GlobalSettings.MediumFontSize;
Font = GlobalSettings.LightFont;
TextColor3 = GlobalSettings.WhiteTextColor;
BackgroundTransparency = 1;
Visible = false;
Parent = tabItem;
}
local function OnSelectionChanged()
if selected then
tabItem.TextColor3 = GlobalSettings.WhiteTextColor;
else
tabItem.TextColor3 = GlobalSettings.BlueTextColor;
end
end
function this:GetContentItem()
return content
end
function this:GetGuiObject()
return tabItem
end
function this:SetSelected(isSelected)
if selected ~= isSelected then
selected = isSelected
OnSelectionChanged()
end
end
function this:GetSelected()
return selected
end
function this:GetName()
return name
end
function this:GetSize()
return tabItem.Size
end
function this:OnClick()
smallText.Visible = true;
tabItem.TextTransparency = 1;
SoundManager:Play('ButtonPress')
end
function this:OnClickRelease()
smallText.Visible = false;
tabItem.TextTransparency = 0;
end
function this:SetPosition(newPosition)
tabItem.Position = newPosition
end
function this:SetParent(newParent)
tabItem.Parent = newParent
end
-- Initialize
OnSelectionChanged()
return this
end
return CreateTabDockItem
@@ -0,0 +1,165 @@
--[[
// TextBox.lua
// Creates a custom TextBox object that uses the platform virtual keyboard.
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function createTextBox(size)
local this = {}
local spacing = Vector2.new()
local defaultText = ""
local keyboardTitle = ""
local keyboardDescription = ""
local keyboardType = Enum.XboxKeyBoardType.Default
local currentInputText = ""
local keyboardClosedCn = nil
local isEnabled = true
this.OnTextChanged = Utility.Signal()
-- need custom selection box to fit with the spacing
local SelectionBox = Utility.Create'ImageLabel'
{
Name = "SelectionBox";
Image = 'rbxasset://textures/ui/SelectionBox.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(21,21,41,41);
BackgroundTransparency = 1;
}
local TextBoxFrame = Utility.Create'ImageButton'
{
Name = "TextBoxFrame";
Size = size;
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.TextBoxColor;
ImageTransparency = GlobalSettings.TextBoxDefaultTransparency;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
SoundManager:CreateSound('MoveSelection');
}
local DefaultTextLabel = Utility.Create'TextLabel'
{
Name = "DefaultTextLabel";
Size = UDim2.new(1, -spacing.x * 2, 1, -spacing.y * 2);
Position = UDim2.new(0, spacing.x, 0, spacing.y);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = 2;
Parent = TextBoxFrame;
}
local function setTextBoxSizeAndPosition()
DefaultTextLabel.Size = UDim2.new(1, -spacing.x * 2, 1, -spacing.y * 2)
DefaultTextLabel.Position = UDim2.new(0, spacing.x, 0, spacing.y)
SelectionBox.Size = UDim2.new(1, spacing.x * 2 + 24, 1, spacing.y * 2 + 24)
SelectionBox.Position = UDim2.new(0, -spacing.x - 12, 0, -spacing.y - 12)
end
setTextBoxSizeAndPosition()
--[[ Input ]]--
local function onKeyboardClosed(inputText)
currentInputText = inputText
if #currentInputText == 0 then
DefaultTextLabel.Text = this:GetDefaultText()
elseif keyboardType == Enum.XboxKeyBoardType.Password then
DefaultTextLabel.Text = string.rep("*", #currentInputText)
else
DefaultTextLabel.Text = currentInputText
end
DefaultTextLabel.Visible = true
Utility.DisconnectEvent(keyboardClosedCn)
this.OnTextChanged:fire(currentInputText)
end
TextBoxFrame.MouseButton1Click:connect(function()
if isEnabled then
if UserSettings().GameSettings:InStudioMode() then
print('Warning: virtual keyboard not accessable in studio')
elseif PlatformService then
DefaultTextLabel.Visible = false
PlatformService:ShowKeyboard(keyboardTitle, keyboardDescription,
currentInputText or "", keyboardType)
end
Utility.DisconnectEvent(keyboardClosedCn)
if PlatformService then
keyboardClosedCn = PlatformService.KeyboardClosed:connect(onKeyboardClosed)
end
end
end)
TextBoxFrame.SelectionGained:connect(function()
Utility.PropertyTweener(TextBoxFrame, "ImageTransparency", GlobalSettings.TextBoxSelectedTransparency,
GlobalSettings.TextBoxSelectedTransparency, 0, Utility.EaseInOutQuad, true)
end)
TextBoxFrame.SelectionLost:connect(function()
Utility.PropertyTweener(TextBoxFrame, "ImageTransparency", GlobalSettings.TextBoxDefaultTransparency,
GlobalSettings.TextBoxDefaultTransparency, 0, Utility.EaseInOutQuad, true)
end)
function this:SetKeyboardTitle(newTitle)
keyboardTitle = newTitle
end
function this:SetKeyboardDescription(newDescription)
keyboardDescription = newDescription
end
function this:SetKeyboardType(newType)
keyboardType = newType
end
function this:SetEnabled(value)
isEnabled = value
end
function this:SetParent(newParent)
TextBoxFrame.Parent = newParent
end
function this:SetPosition(newPosition)
TextBoxFrame.Position = newPosition
end
function this:SetSpacing(newSpacing)
spacing = newSpacing
setTextBoxSizeAndPosition()
end
function this:SetDefaultText(newText)
defaultText = newText
DefaultTextLabel.Text = defaultText
end
function this:SetFont(newFont)
TextBox.Font = newFont
end
function this:SetFontSize(newFontSize)
TextBox.FontSize = newFontSize
end
function this:GetContainer()
return TextBoxFrame
end
function this:GetTextBox()
return TextBox
end
function this:GetDefaultText()
return defaultText
end
return this
end
return createTextBox
@@ -0,0 +1,155 @@
--[[
// ThumbnailLoader.lua
// Creates a thumbnail loader object that handles the loading
// of thumb nails.
// Thumbnails may not yet be generated, so this will retry generation and
// assign the final thumbnail
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContentProvider = game:GetService('ContentProvider')
local Http = require(Modules:FindFirstChild('Http'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local Utility = require(Modules:FindFirstChild('Utility'))
local ThumbnailLoader = {}
local BaseUrl = Http.BaseUrl
local AssetGameBaseUrl = Http.AssetGameBaseUrl
local RETRIES = 6
local FORMAT = "png"
local FADE_IN_TIME = 0.25
--[[ Sizes ]]--
ThumbnailLoader.Sizes = {
Small = Vector2.new(100, 100);
Medium = Vector2.new(250, 250);
Large = Vector2.new(576, 324);
}
ThumbnailLoader.SubstitutionTypeResult = {
None = 0;
Unapproved = 1;
PendingReview = 2;
Broken = 3;
Unavailable = 4;
Unknown = 5;
}
ThumbnailLoader.AssetType = {
Icon = { IsFinal = Http.GetAssetThumbnailFinalAsync;
SetImageUrl = 'Thumbs/Asset.ashx?width=%d&height=%d&assetId=%d&ignorePlaceMediaItems=true'; };
Avatar = { IsFinal = Http.GetAssetAvatarFinalAsync;
SetImageUrl = 'Thumbs/Avatar.ashx?width=%d&height=%d&userId=%d&ignorePlaceMediaItems=true'; };
Outfit = { IsFinal = Http.GetOutfitThumbnailFinalAsync;
SetImageUrl = 'Thumbs/Avatar.ashx?width=%d&height=%d&userId=%d&ignorePlaceMediaItems=true'; };
}
--[[
imageObject - a roblox gui image object (ImageLabel, ImageButton)
assetId - the id of the asset you want an image for
size - a ThumbnailLoader.Sizes
assetType - a ThumbnailLoader.AssetType
]]
function ThumbnailLoader:Create(imageObject, assetId, size, assetType, cachebust)
local this = {}
local isLoading = false
local cancelled = false
local isFinalSuccess = false
local uri = AssetGameBaseUrl..string.format(assetType.SetImageUrl, size.x, size.y, assetId or -1)
if cachebust then
uri = uri .. '&cb=' .. tostring(tick())
end
local getIsFinalFunc = assetType.IsFinal
local function preloadThumbnailAsync()
local preloadTable = { uri }
ContentProvider:PreloadAsync(preloadTable)
end
local function tryGetFinalAsync()
local result = getIsFinalFunc(assetId, size.x, size.y, FORMAT)
if result then
local isFinal = result["Final"] or result["thumbnailFinal"]
local substitutionType = result["substitutionType"] or result["SubstitutionType"]
if isFinal == true and
(substitutionType == nil or substitutionType == ThumbnailLoader.SubstitutionTypeResult.None) then
isFinalSuccess = true
preloadThumbnailAsync()
return true
end
end
return false
end
local function loadThumbInternalAsync()
local tryCount = 0
isLoading = true
isFinalSuccess = false
while tryCount <= RETRIES and isLoading and not cancelled do
if tryGetFinalAsync() then
break
end
tryCount = tryCount + 1
wait(tryCount ^ 2)
end
isLoading = false
end
function this:LoadAsync(showSpinner, fadeImage, spinnerProperties)
spinnerProperties = spinnerProperties or {}
if not assetId then return end
if showSpinner == nil then
showSpinner = true
end
if fadeImage == nil then
fadeImage = true
end
-- reset image
imageObject.Image = ""
if fadeImage then
local tween = Utility.PropertyTweener(imageObject, "ImageTransparency", 1, 1, 0,
Utility.EaseInOutQuad, true, nil)
end
-- try first time before starting loading widget
if not tryGetFinalAsync() then
if showSpinner then
spinnerProperties['Parent'] = spinnerProperties['Parent'] or imageObject
local loader = LoadingWidget(
spinnerProperties,
{ loadThumbInternalAsync } )
loader:AwaitFinished()
loader:Cleanup()
else
loadThumbInternalAsync()
end
end
if not cancelled then
imageObject.Image = isFinalSuccess and uri or ""
if fadeImage then
local tween = Utility.PropertyTweener(imageObject, "ImageTransparency", 1, 0, FADE_IN_TIME,
Utility.EaseInOutQuad, true, nil)
end
end
return isFinalSuccess
end
function this:Cancel()
isLoading = false
cancelled = true
end
return this
end
return ThumbnailLoader
@@ -0,0 +1,108 @@
--[[
// UnlinkAccountOverlay.lua
// Confirmation overlay for when you unlink your account
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local GuiService = game:GetService('GuiService')
local ContextActionService = game:GetService("ContextActionService")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseOverlay = require(Modules:FindFirstChild('BaseOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local function createUnlinkAccountOverlay(titleAndMsg)
local this = BaseOverlay()
local title = titleAndMsg.Title
local message = titleAndMsg.Msg
local errorIcon = Utility.Create'ImageLabel'
{
Name = "ReportIcon";
Position = UDim2.new(0, 226, 0, 204);
BackgroundTransparency = 1;
ZIndex = this.BaseZIndex;
}
AssetManager.LocalImage(errorIcon, 'rbxasset://textures/ui/Shell/Icons/ErrorIconLargeCopy',
{['720'] = UDim2.new(0,214,0,176); ['1080'] = UDim2.new(0,321,0,264);})
this:SetImage(errorIcon)
local titleText = Utility.Create'TextLabel'
{
Name = "TitleText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, this.RightAlign, 0, 136);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.HeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = title;
TextXAlignment = Enum.TextXAlignment.Left;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local descriptionText = Utility.Create'TextLabel'
{
Name = "DescriptionText";
Size = UDim2.new(0, 762, 0, 304);
Position = UDim2.new(0, this.RightAlign, 0, titleText.Position.Y.Offset + 62);
BackgroundTransparency = 1;
TextXAlignment = Enum.TextXAlignment.Left;
TextYAlignment = Enum.TextYAlignment.Top;
Font = GlobalSettings.LightFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
TextWrapped = true;
Text = message;
ZIndex = this.BaseZIndex;
Parent = this.Container;
}
local okButton = Utility.Create'TextButton'
{
Name = "OkButton";
Size = UDim2.new(0, 320, 0, 66);
Position = UDim2.new(0, this.RightAlign, 1, -100 - 66);
BorderSizePixel = 0;
BackgroundColor3 = GlobalSettings.BlueButtonColor;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(Strings:LocalizedString("ConfirmWord"));
ZIndex = this.BaseZIndex;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
}
--[[ Input Events ]]--
local okButtonDebounce
okButton.MouseButton1Click:connect(function()
if this:Close() then
EventHub:dispatchEvent(EventHub.Notifications["UnlinkAccountConfirmation"])
end
end)
local baseFocus = this.Focus
function this:Focus()
baseFocus(self)
GuiService.SelectedCoreObject = okButton
end
function this:GetOverlaySound()
return 'Error'
end
return this
end
return createUnlinkAccountOverlay
@@ -0,0 +1,178 @@
--[[
// UnlinkAccountScreen.lua
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local ContextActionService = game:GetService('ContextActionService')
local GuiService = game:GetService('GuiService')
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local BaseScreen = require(Modules:FindFirstChild('BaseScreen'))
local Errors = require(Modules:FindFirstChild('Errors'))
local ErrorOverlay = require(Modules:FindFirstChild('ErrorOverlay'))
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local LoadingWidget = require(Modules:FindFirstChild('LoadingWidget'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local TextBox = require(Modules:FindFirstChild('TextBox'))
local UnlinkAccountOverlay = require(Modules:FindFirstChild('UnlinkAccountOverlay'))
local Utility = require(Modules:FindFirstChild('Utility'))
local UserData = require(Modules:FindFirstChild('UserData'))
local function createUnlinkAccountScreen()
local this = BaseScreen()
this:SetTitle(string.upper(Strings:LocalizedString("AccountSettingsTitle")))
local gamerTag = UserData:GetDisplayName() or ""
local robloxName = UserData:GetRobloxName() or ""
local linkedAsPhrase = string.format(Strings:LocalizedString('LinkedAsPhrase'), gamerTag, robloxName)
local unlinkButtonText = string.format(Strings:LocalizedString("UnlinkGamerTagPhrase"), gamerTag)
local dummySelection = Utility.Create'Frame'
{
BackgroundTransparency = 1;
}
local ModalOverlay = Utility.Create'Frame'
{
Name = "ModalOverlay";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = GlobalSettings.ModalBackgroundTransparency;
BackgroundColor3 = GlobalSettings.ModalBackgroundColor;
BorderSizePixel = 0;
ZIndex = 4;
}
local LinkedAsText = Utility.Create'TextLabel'
{
Name = "LinkedAsText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0.5, 0, 0, 264);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.TitleSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Text = linkedAsPhrase;
Parent = this.Container;
}
local GamerPic = Utility.Create'ImageLabel'
{
Name = "GamerPic";
Size = UDim2.new(0, 300, 0, 300);
BackgroundTransparency = 0;
BorderSizePixel = 0;
Image = 'rbxapp://xbox/localgamerpic';
Parent = this.Container;
}
Utility.CalculateAnchor(GamerPic, UDim2.new(0.5, 0, 0, LinkedAsText.Position.Y.Offset + 52), Utility.Enum.Anchor.TopMiddle)
local UnlinkButton = Utility.Create'ImageButton'
{
Name = "UnlinkButton";
Size = UDim2.new(0, 360, 0, 64);
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.GreySelectedButtonColor;
Image = 'rbxasset://textures/ui/Shell/Buttons/Generic9ScaleButton@720.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(Vector2.new(4, 4), Vector2.new(28, 28));
ZIndex = 2;
Parent = this.Container;
SoundManager:CreateSound('MoveSelection');
AssetManager.CreateShadow(1)
}
Utility.CalculateAnchor(UnlinkButton, UDim2.new(0.5, 0, 0, GamerPic.Position.Y.Offset + GamerPic.Size.Y.Offset + 35), Utility.Enum.Anchor.TopMiddle)
local UnlinkText = Utility.Create'TextLabel'
{
Name = "UnlinkText";
Size = UDim2.new(1, 0, 1, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.ButtonSize;
TextColor3 = GlobalSettings.TextSelectedColor;
Text = string.upper(unlinkButtonText);
ZIndex = 2;
Parent = UnlinkButton;
}
local isUnlinking = false
local function unlinkAccountAsync()
if isUnlinking then return end
isUnlinking = true
local unlinkResult = nil
local loader = LoadingWidget(
{ Parent = this.Container }, {
function()
unlinkResult = AccountManager:UnlinkAccountAsync()
end
})
-- set up full screen loader
ModalOverlay.Parent = GuiRoot
ContextActionService:BindCoreAction("BlockB", function() end, false, Enum.KeyCode.ButtonB)
UnlinkButton.SelectionImageObject = dummySelection
UnlinkButton.ImageColor3 = GlobalSettings.GreyButtonColor
UnlinkText.TextColor3 = GlobalSettings.WhiteTextColor
-- call loader
loader:AwaitFinished()
-- clean up
-- NOTE: Unlink success will fire the UserAccountChanged event. This event will fire and listeners will
-- run before the loader is finished. The below code needs to run in case of errors, but on success
-- will not interfere with the reauth logic in AppHome.lua
loader:Cleanup()
loader = nil
UnlinkButton.SelectionImageObject = nil
UnlinkButton.ImageColor3 = GlobalSettings.GreySelectedButtonColor
UnlinkText.TextColor3 = GlobalSettings.TextSelectedColor
ContextActionService:UnbindCoreAction("BlockB")
ModalOverlay.Parent = nil
if unlinkResult ~= AccountManager.AuthResults.Success then
local err = unlinkResult and Errors.Authentication[unlinkResult] or Errors.Default
ScreenManager:OpenScreen(ErrorOverlay(err), false)
end
isUnlinking = false
end
UnlinkButton.MouseButton1Click:connect(function()
if isUnlinking then return end
SoundManager:Play('ButtonPress')
local confirmTitleAndMsg = { Title = Strings:LocalizedString("UnlinkTitle"), Msg = Strings:LocalizedString("UnlinkPhrase") }
ScreenManager:OpenScreen(UnlinkAccountOverlay(confirmTitleAndMsg), false)
end)
--[[ Public API ]]--
-- Override
function this:GetDefaultSelectionObject()
return UnlinkButton
end
-- Override
local baseFocus = this.Focus
function this:Focus()
baseFocus(self)
EventHub:addEventListener(EventHub.Notifications["UnlinkAccountConfirmation"], "unlinkAccount",
function()
unlinkAccountAsync()
end)
end
-- Override
local baseRemoveFocus = this.RemoveFocus
function this:RemoveFocus()
baseRemoveFocus(self)
EventHub:removeEventListener(EventHub.Notifications["UnlinkAccountConfirmation"], "unlinkAccount")
end
return this
end
return createUnlinkAccountScreen
@@ -0,0 +1,225 @@
--[[
// UserData.lua
// API for all user related data
// TODO:
Update all local create calls to use GetLocalUserData() and update
.Create()
Remove all friends stuff and move to new module
]]
local CoreGui = game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local Players = game:GetService('Players')
local HttpService = game:GetService('HttpService')
local PlatformService = nil
pcall(function() PlatformService = game:GetService('PlatformService') end)
local AccountManager = require(Modules:FindFirstChild('AccountManager'))
local Http = require(Modules:FindFirstChild('Http'))
local BaseUrl = Http.BaseUrl
local UserData = {}
local currentUserData = nil
local CONSTANT_RETRY_TIME = 30
local function getLocalPlayer()
local plr = Players.LocalPlayer
if not plr then
while not plr do
wait()
plr = Players.LocalPlayer
end
end
return plr
end
local function setVoteCountAsync()
local voteResult = Http.GetVoteCountAsync()
currentUserData["VoteCount"] = voteResult and voteResult['VoteCount'] or 0
end
local function verifyHasLinkedAccountAsync()
local result = AccountManager:HasLinkedAccountAsync()
while result ~= AccountManager.AuthResults.Success and result ~= AccountManager.AuthResults.AccountUnlinked do
result = AccountManager:HasLinkedAccountAsync()
wait(CONSTANT_RETRY_TIME)
end
currentUserData["LinkedAccountResult"] = result
end
local function verifyHasRobloxCredentialsAsync()
local result = AccountManager:HasRobloxCredentialsAsync()
while result ~= AccountManager.AuthResults.Success and result ~= AccountManager.AuthResults.UsernamePasswordNotSet do
result = AccountManager:HasRobloxCredentialsAsync()
wait(CONSTANT_RETRY_TIME)
end
currentUserData["RobloxCredentialsResult"] = result
end
function UserData:Initialize()
if currentUserData then
print("Trying to initialize UserData when we already have valid data.")
end
currentUserData = {}
if UserSettings().GameSettings:InStudioMode() then
local localPlayer = getLocalPlayer()
currentUserData["Gamertag"] = "InStudioNoGamertag"
currentUserData["RbxUid"] = localPlayer.userId
currentUserData["RobloxName"] = localPlayer.Name
spawn(function()
setVoteCountAsync()
end)
elseif PlatformService then
local userInfo = PlatformService:GetPlatformUserInfo()
if userInfo then
currentUserData["Gamertag"] = userInfo["Gamertag"]
currentUserData["RbxUid"] = userInfo["RobloxUserId"]
else
currentUserData["Gamertag"] = Players.LocalPlayer.Name
end
spawn(setVoteCountAsync)
spawn(verifyHasLinkedAccountAsync)
spawn(verifyHasRobloxCredentialsAsync)
spawn(function()
currentUserData["RobloxName"] = Players:GetNameFromUserIdAsync(currentUserData["RbxUid"])
end)
end
end
function UserData:GetRbxUserId()
if not currentUserData then
print("Error: UserData:GetRbxUserId() - UserData has not been initialized. Don't do that!")
return nil
end
return currentUserData["RbxUid"]
end
function UserData:GetDisplayName()
if not currentUserData then
print("Error: UserData:GetDisplayName() - UserData has not been initialized. Don't do that!")
return nil
end
return currentUserData["Gamertag"]
end
function UserData:GetRobloxName()
if not currentUserData then
print("Error: UserData:GetRobloxName() - UserData has not been initialized. Don't do that!")
return nil
end
return currentUserData["RobloxName"]
end
function UserData:SetRobloxName(name)
if currentUserData then
currentUserData["RobloxName"] = name
spawn(function()
currentUserData["RobloxName"] = Players:GetNameFromUserIdAsync(currentUserData["RbxUid"])
end)
end
end
function UserData:GetAvatarUrl(width, height)
-- TODO: Update when Thumbnail manager gets fixed
if not currentUserData then
print("Error: UserData:GetAvatarUrl() - UserData has not been initialized. Don't do that!")
return nil
end
return Http.AssetGameBaseUrl..'Thumbs/Avatar.ashx?userid='..tostring(currentUserData.RbxUid)..
'&width='..tostring(width)..'&height='..tostring(height)
end
function UserData:GetVoteCount()
if not currentUserData then
print("Error: UserData:GetVoteCount() - UserData has not been initialized. Don't do that!")
return nil
end
return currentUserData["VoteCount"]
end
function UserData:IncrementVote()
currentUserData["VoteCount"] = (currentUserData["VoteCount"] or 0) + 1
end
function UserData:DecrementVote()
currentUserData["VoteCount"] = math.max((currentUserData["VoteCount"] or 0) - 1, 0)
end
-- returns true, false or nil in the case of error
function UserData:HasLinkedAccount()
local result = currentUserData["LinkedAccountResult"]
if result == AccountManager.AuthResults.Success then
return true
elseif result == AccountManager.AuthResults.AccountUnlinked then
return false
else
return nil
end
end
-- returns true, false or nil in the case of error
function UserData:HasRobloxCredentials()
local result = currentUserData["RobloxCredentialsResult"]
if result == AccountManager.AuthResults.Success then
return true
elseif result == AccountManager.AuthResults.UsernamePasswordNotSet then
return false
else
return nil
end
end
function UserData:SetHasRobloxCredentials(value)
currentUserData["RobloxCredentialsResult"] = value
end
function UserData:Reset()
currentUserData = nil
end
--[[ This should no longer be used ]]--
function UserData.GetLocalUserIdAsync()
return UserData.GetLocalPlayerAsync().userId
end
function UserData.GetLocalPlayerAsync()
local localPlayer = Players.LocalPlayer
while not localPlayer do
wait()
localPlayer = Players.LocalPlayer
end
return localPlayer
end
function UserData.GetPlatformUserBalanceAsync()
local result = Http.GetPlatformUserBalanceAsync()
if not result then
-- TODO: Error Code
return nil
end
--
return result["Robux"]
end
function UserData.GetTotalUserBalanceAsync()
local result = Http.GetTotalUserBalanceAsync()
if not result then
return nil
end
return result["robux"]
end
return UserData
@@ -0,0 +1,470 @@
-- Written by Kip Turner, Copyright ROBLOX 2015
local RunService = game:GetService('RunService')
local GuiService = game:GetService('GuiService')
local Util = {}
do
function Util.IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
function Util.CalculateRelativeDimensions(guiObject, guiDims, mockup_dims)
local guiResolution = GuiService:GetScreenResolution()
local absolutePercentSize = (guiDims / mockup_dims)
if mockup_dims.y > 0 and guiResolution.y > 0 then
local mockupAspectRatio = mockup_dims.x / mockup_dims.y
local globalAspectRatio = guiResolution.x / guiResolution.y
absolutePercentSize = absolutePercentSize * (mockupAspectRatio / globalAspectRatio)
local parentObject = guiObject.Parent
if parentObject then
local parentPercentScreen = parentObject.AbsoluteSize / guiResolution
local parentSizeInverse = 1 / parentPercentScreen
if Util.IsFinite(parentSizeInverse.x) and Util.IsFinite(parentSizeInverse.y) then
return UDim2.new(parentSizeInverse.x * absolutePercentSize.x, 0, parentSizeInverse.y * absolutePercentSize.y, 0)
end
end
end
return UDim2.new(absolutePercentSize.x, 0, absolutePercentSize.y, 0)
end
-- Anchor Graph
-- 1 2 3
-- 4 5 6
-- 7 8 9
Util.Enum =
{
Anchor =
{
TopLeft = 1;
TopMiddle = 2;
TopRight = 3;
CenterLeft = 4;
Center = 5;
CenterRight = 6;
BottomLeft = 7;
BottomMiddle = 8;
BottomRight = 9;
};
};
function Util.CalculateAnchor(imageObject, position, anchorType)
-- TODO: make this work with relativeXX, relativeYY
if anchorType == Util.Enum.Anchor.TopLeft then
imageObject.Position = position
elseif anchorType == Util.Enum.Anchor.TopMiddle then
imageObject.Position = position + UDim2.new(-imageObject.Size.X.Scale / 2, -imageObject.Size.X.Offset / 2,
0, 0);
elseif anchorType == Util.Enum.Anchor.TopRight then
imageObject.Position = position + UDim2.new(imageObject.Size.X.Scale, -imageObject.Size.X.Offset,
0, 0);
elseif anchorType == Util.Enum.Anchor.CenterLeft then
imageObject.Position = position + UDim2.new(0, 0,
-imageObject.Size.Y.Scale / 2, -imageObject.Size.Y.Offset / 2);
elseif anchorType == Util.Enum.Anchor.Center then
imageObject.Position = position + UDim2.new(-imageObject.Size.X.Scale / 2, -imageObject.Size.X.Offset / 2,
-imageObject.Size.Y.Scale / 2, -imageObject.Size.Y.Offset / 2);
elseif anchorType == Util.Enum.Anchor.CenterRight then
imageObject.Position = position + UDim2.new(-imageObject.Size.X.Scale, -imageObject.Size.X.Offset,
-imageObject.Size.Y.Scale / 2, -imageObject.Size.Y.Offset / 2);
elseif anchorType == Util.Enum.Anchor.BottomLeft then
imageObject.Position = position + UDim2.new(0, 0,
-imageObject.Size.Y.Scale, -imageObject.Size.Y.Offset);
elseif anchorType == Util.Enum.Anchor.BottomMiddle then
imageObject.Position = position + UDim2.new(-imageObject.Size.X.Scale / 2, -imageObject.Size.X.Offset / 2,
-imageObject.Size.Y.Scale, -imageObject.Size.Y.Offset);
elseif anchorType == Util.Enum.Anchor.BottomRight then
imageObject.Position = position + UDim2.new(-imageObject.Size.X.Scale, -imageObject.Size.X.Offset,
-imageObject.Size.Y.Scale, -imageObject.Size.Y.Offset);
end
end
function Util.CalculateFit(containerObject, rawImageSize)
local absSize = containerObject.AbsoluteSize
local scalar = absSize / rawImageSize
local fixedSize = rawImageSize * math.min(scalar.X, scalar.Y)
return UDim2.new(0, fixedSize.X , 0, fixedSize.Y)
end
function Util.CalculateFill(containerObject, rawImageSize)
local absSize = containerObject.AbsoluteSize
local scalar = absSize / rawImageSize
local fixedSize = rawImageSize * math.max(scalar.X, scalar.Y)
return UDim2.new(0, fixedSize.X , 0, fixedSize.Y)
end
function Util.Create(instanceType)
return function(data)
local obj = Instance.new(instanceType)
for k, v in pairs(data) do
if type(k) == 'number' then
v.Parent = obj
else
obj[k] = v
end
end
return obj
end
end
---- TWEENING ----
local ActiveTweens = {}
local function getActiveTween(prop, instance)
return ActiveTweens[prop] and ActiveTweens[prop][instance]
end
local function setActiveTween(prop, instance, newTween)
if not ActiveTweens[prop] then
ActiveTweens[prop] = {}
end
ActiveTweens[prop][instance] = newTween
end
function Util.Linear(t, b, c, d)
if t >= d then return b + c end
return c*t/d + b
end
function Util.EaseOutQuad(t, b, c, d)
if t >= d then return b + c end
t = t/d;
return -c * t*(t-2) + b
end
function Util.EaseInOutQuad(t, b, c, d)
if t >= d then return b + c end
t = t / (d/2);
if (t < 1) then return c/2*t*t + b end;
t = t - 1;
return -c/2 * (t*(t-2) - 1) + b;
end
function Util.PropertyTweener(instance, prop, start, final, duration, easingFunc, override, callbackFunction)
easingFunc = easingFunc or Util.Linear
override = override or false
local this = {}
this.StartTime = tick()
this.EndTime = this.StartTime + duration
this.Cancelled = false
local finished = false
local percentComplete = 0
local function setValue(newValue)
if instance then
instance[prop] = newValue
end
end
local function finalize()
setValue(easingFunc(1, start, final - start, 1))
finished = true
percentComplete = 1
if getActiveTween(prop, instance) == this then
setActiveTween(prop, instance, nil)
end
if callbackFunction then
callbackFunction()
end
end
if override or not getActiveTween(prop, instance) then
if getActiveTween(prop, instance) then
getActiveTween(prop, instance):Cancel()
end
setActiveTween(prop, instance, this)
-- Initial set
setValue(easingFunc(0, start, final - start, duration))
spawn(function()
local now = tick()
while now < this.EndTime and instance and not this.Cancelled do
setValue(easingFunc(now - this.StartTime, start, final - start, duration))
percentComplete = Util.Clamp(0, 1, (now - this.StartTime) / duration)
RunService.RenderStepped:wait()
now = tick()
end
if this.Cancelled == false and instance then
finalize()
end
if getActiveTween(prop, instance) == this then
setActiveTween(prop, instance, nil)
end
end)
else
finished = true
end
function this:GetFinal()
return final
end
function this:GetPercentComplete()
return percentComplete
end
function this:IsFinished()
return finished
end
function this:Finish()
if not finished then
self:Cancel()
finalize()
end
end
function this:Cancel()
this.Cancelled = true
finished = true
if getActiveTween(prop, instance) == this then
setActiveTween(prop, instance, nil)
end
end
return this
end
---------------
--- EVENTS ----
function Util.Signal()
local sig = {}
local mSignaler = Instance.new('BindableEvent')
local mArgData = nil
local mArgDataCount = nil
function sig:fire(...)
mArgData = {...}
mArgDataCount = select('#', ...)
mSignaler:Fire()
end
function sig:connect(f)
if not f then error("connect(nil)", 2) end
return mSignaler.Event:connect(function()
f(unpack(mArgData, 1, mArgDataCount))
end)
end
function sig:wait()
mSignaler.Event:wait()
assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
return unpack(mArgData, 1, mArgDataCount)
end
return sig
end
function Util.DisconnectEvent(conn)
if conn then
conn:disconnect()
end
return nil
end
--------------
-- MATH --
function Util.Clamp(low, high, input)
return math.max(low, math.min(high, input))
end
function Util.ClampVector2(low, high, input)
return Vector2.new(Util.Clamp(low.x, high.x, input.x), Util.Clamp(low.y, high.y, input.y))
end
function Util.TweenPositionOrSet(guiObject, ...)
if guiObject:IsDescendantOf(game) then
guiObject:TweenPosition(...)
else
guiObject.Position = select(1, ...)
end
end
----
function Util.ClampCanvasPosition(scrollingContainer, position)
local container = scrollingContainer
local parentSize = container.Parent and container.Parent.AbsoluteSize or Vector2.new(0,0)
local absoluteCanvasSize = Vector2.new(container.CanvasSize.X.Scale * parentSize.X + container.CanvasSize.X.Offset,
container.CanvasSize.Y.Scale * parentSize.Y + container.CanvasSize.Y.Offset)
local nextX = Util.Clamp(0, absoluteCanvasSize.X - container.AbsoluteWindowSize.X, position.X)
local nextY = Util.Clamp(0, absoluteCanvasSize.Y - container.AbsoluteWindowSize.Y, position.Y)
return Vector2.new(nextX, nextY)
end
function Util.Round(num, roundToNearest)
roundToNearest = roundToNearest or 1
return math.floor((num + roundToNearest/2) / roundToNearest) * roundToNearest
end
--------------
-- FORMATING --
-- Removed whitespace from the beginning and end of the string
function Util.ChompString(str)
return tostring(str):gsub("^%s+" , ""):gsub("%s+$" , "")
end
function Util.FormatNumberString(value)
-- Make sure beginning and end of the string is clipped
local stringValue = Util.ChompString(value)
return stringValue:reverse():gsub("%d%d%d", "%1,"):reverse():gsub("^,", "")
end
-- PrettyPrint function for formatting data structures into flat strings, usefull for debugging
local function PrettyPrint(tb)
if type(tb) == 'table' then
local str = "{"
for k, v in pairs(tb) do
str = ((str == "{") and str or str..", ")
if type(k) == 'string' then
str = str..k.." = "
elseif type(k) == 'number' then
-- nothing
else
str = str.."["..k.."] = "
end
str = str..PrettyPrint(v)
end
return str.."}"
elseif type(tb) == 'string' then
return "'"..tb.."'"
else
return tostring(tb)
end
end
Util.PrettyPrint = PrettyPrint
-- K is a tunable parameter that changes the shape of the S-curve
-- the larger K is the more straight/linear the curve gets
local function SCurveTranform(t, k, lowerK)
k = k or 0.35
lowerK = lowerK or 0.8
t = Util.Clamp(-1,1,t)
if t >= 0 then
return (k*t) / (k - t + 1)
end
return -((lowerK*-t) / (lowerK + t + 1))
end
local function toSCurveSpace(t, deadzone)
deadzone = deadzone or 0.1
return (1 + deadzone) * (2*math.abs(t) - 1) - deadzone
end
local function fromSCurveSpace(t)
return t/2 + 0.5
end
function Util.GamepadLinearToCurve(thumbstickPosition, deadzone, k, lowerK)
local function onAxis(axisValue)
local sign = axisValue < 0 and -1 or 1
local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue), deadzone)), k, lowerK)
return Util.Clamp(-1,1, point * sign)
end
return Vector2.new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
end
function Util.IsFastFlagEnabled(flagName)
local success, isFlagEnabled = pcall(function()
return settings():GetFFlag(flagName)
end)
if success and not isFlagEnabled then
print("Fast Flag:", flagName, "is currently not enabled.")
elseif not success then
print("GetFFlag failed for flag:", flagName)
end
return success and isFlagEnabled
end
function Util.GetFastVariable(variableName)
local success, value = pcall(function()
return settings():GetFVariable(variableName)
end)
return success and value
end
function Util.ExponentialRepeat(loopPredicate, loopBody, repeatCount)
repeatCount = repeatCount or 6
local retryCount = 1
local numRetries = repeatCount
while retryCount <= numRetries and loopPredicate() do
local done = loopBody()
if done then return end
wait(retryCount ^ 2)
retryCount = retryCount + 1
end
end
function Util.SplitString(str, sep)
local result = {}
if str and sep then
for word in string.gmatch(str, '([^' .. sep .. ']+)') do
table.insert(result, word)
end
end
return result
end
local function findAssetsHelper(object, result, baseUrl)
if not object then return end
if object:IsA('CharacterMesh') then
table.insert(result, baseUrl .. tostring(object.MeshId))
table.insert(result, baseUrl .. tostring(object.BaseTextureId))
table.insert(result, baseUrl .. tostring(object.OverlayTextureId))
elseif object:IsA('FileMesh') then
table.insert(result, object.MeshId)
table.insert(result, object.TextureId)
elseif object:IsA('Decal') then
table.insert(result, object.Texture)
elseif object:IsA('Pants') then
table.insert(result, object.PantsTemplate)
elseif object:IsA('Shirt') then
table.insert(result, object.ShirtTemplate)
end
for _, child in pairs(object:GetChildren()) do
findAssetsHelper(child, result, baseUrl)
end
end
function Util.FindAssetsInModel(object, baseUrl)
baseUrl = baseUrl or 'http://www.watrbx.wtf/asset/?id='
local result = {}
findAssetsHelper(object, result, baseUrl)
return result
end
function Util.ConvertFontSizeEnumToInt(fontSizeEnum)
local name = fontSizeEnum.Name
-- TODO: this is sort of gross?
local result = string.match(name, '%d+')
return result or 10
end
end
return Util
@@ -0,0 +1,74 @@
--[[
// VoteFrame.lua
// Creates a vote frame for a game
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local CreateVoteFrame = function(parent, position)
local this = {}
-- Assume 1080p
local MAX_SIZE = 203
local voteContainer = Utility.Create'Frame'
{
Name = "VoteContainer";
Size = UDim2.new(0, MAX_SIZE, 0, 16);
Position = position;
BackgroundTransparency = 1;
Visible = false;
Parent = parent;
}
local batteryImageRed = Utility.Create'ImageLabel'
{
Name = "BatteryImageRed";
BackgroundTransparency = 1;
ImageColor3 = GlobalSettings.RedTextColor;
Parent = voteContainer;
}
AssetManager.LocalImage(batteryImageRed,
'rbxasset://textures/ui/Shell/Icons/RatingBar', {['720'] = UDim2.new(0,134,0,11); ['1080'] = UDim2.new(0,203,0,16);})
local batteryImageGreen = batteryImageRed:Clone()
batteryImageGreen.ImageColor3 = GlobalSettings.GreenTextColor
batteryImageGreen.ZIndex = 2
batteryImageGreen.Parent = batteryImageRed
--[[ Public API ]]--
function this:SetPercentFilled(percent)
if not percent then
batteryImageGreen.Size = batteryImageRed.Size
batteryImageGreen.ImageRectSize = Vector2.new(0, 0)
batteryImageGreen.ImageColor3 = GlobalSettings.GreyTextColor
else
percent = Utility.Round(percent, 0.1)
local drawSize = math.floor(percent * MAX_SIZE)
batteryImageGreen.ImageColor3 = GlobalSettings.GreenTextColor
batteryImageGreen.Size = UDim2.new(0, drawSize, 0, batteryImageGreen.Size.Y.Offset)
batteryImageGreen.ImageRectSize = Vector2.new(drawSize, 0)
end
end
function this:SetVisible(value)
voteContainer.Visible = value
end
function this:GetContainer()
return voteContainer
end
function this:Destroy()
voteContainer:Destroy()
this = nil
end
return this
end
return CreateVoteFrame
@@ -0,0 +1,278 @@
--[[
// VoteView.lua
// Manages the vote view for the game details page
]]
local CoreGui = Game:GetService("CoreGui")
local GuiRoot = CoreGui:FindFirstChild("RobloxGui")
local Modules = GuiRoot:FindFirstChild("Modules")
local EventHub = require(Modules:FindFirstChild('EventHub'))
local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings'))
local Strings = require(Modules:FindFirstChild('LocalizedStrings'))
local Utility = require(Modules:FindFirstChild('Utility'))
local VoteFrame = require(Modules:FindFirstChild('VoteFrame'))
local AssetManager = require(Modules:FindFirstChild('AssetManager'))
local ScreenManager = require(Modules:FindFirstChild('ScreenManager'))
local ErrorOverlayModule = require(Modules:FindFirstChild('ErrorOverlay'))
local Errors = require(Modules:FindFirstChild('Errors'))
local SoundManager = require(Modules:FindFirstChild('SoundManager'))
local UserData = require(Modules:FindFirstChild('UserData'))
local function createVoteView()
local this = {}
local canVote = false
local myVote = nil
local upVotes = 0
local downVotes = 0
local thisGameData = nil
local defaultSelection = nil
this.Container = Utility.Create'Frame'
{
Name = "VoteContainer";
Size = UDim2.new(1, 0, 0, 114);
BackgroundTransparency = 1;
Visible = false;
}
-- Can't Vote Objects
-- override selection image
local SelectionImage = Utility.Create'ImageLabel'
{
Name = "SelectionImage";
Size = UDim2.new(1, 32, 1, 32);
Position = UDim2.new(0, -16, 0, -16);
Image = 'rbxasset://textures/ui/SelectionBox.png';
ScaleType = Enum.ScaleType.Slice;
SliceCenter = Rect.new(21,21,41,41);
BackgroundTransparency = 1;
}
local CannotVoteSelection = Utility.Create'Frame'
{
Name = "CannotVoteSelection";
Size = UDim2.new(1, 0, 0, 94);
Position = UDim2.new(0, 0, 0, 5);
BackgroundTransparency = 1;
Selectable = true;
SelectionImageObject = SelectionImage;
SoundManager:CreateSound('MoveSelection');
}
local CannotVoteText = Utility.Create'TextLabel'
{
Name = "CannotVoteText";
Size = UDim2.new(0.8, 0, 1, 0);
Position = UDim2.new(0.1, 0, 0, 0);
BackgroundTransparency = 1;
Font = GlobalSettings.BoldFont;
FontSize = GlobalSettings.SubHeaderSize;
TextColor3 = GlobalSettings.WhiteTextColor;
Visible = false;
TextWrapped = true;
Text = Strings:LocalizedString("CannotVoteWord");
Parent = CannotVoteSelection;
}
-- Vote Objects
local VoteWidget = VoteFrame(this.Container, UDim2.new(0, 88, 0, 34))
local VoteContainer = VoteWidget:GetContainer()
local ThumbsUpImage = Utility.Create'ImageLabel'
{
Name = "ThumbsUpImage";
Size = UDim2.new(0, 48, 0, 48);
BackgroundTransparency = 1;
}
local ThumbsDownImage = ThumbsUpImage:Clone()
ThumbsDownImage.Name = "ThumbsDownImage"
-- Buttons act as buffers for selection gui
local ThumbsUpButton = Utility.Create'ImageButton'
{
Name = "ThumbsUpButton";
Size = UDim2.new(0, ThumbsUpImage.Size.X.Offset + 18, 0, ThumbsUpImage.Size.Y.Offset + 18);
BackgroundTransparency = 1;
Image = "";
Parent = VoteContainer;
SoundManager:CreateSound('MoveSelection');
}
ThumbsUpButton.Position = UDim2.new(0, -ThumbsUpButton.Size.X.Offset - 12, 0,
-ThumbsUpButton.Size.Y.Offset / 2 + VoteContainer.Size.Y.Offset / 2)
local ThumbsDownButton = ThumbsUpButton:Clone()
ThumbsDownButton.Name = "ThumbsDownButton"
ThumbsDownButton.Position = UDim2.new(1, 12, 0, ThumbsDownButton.Position.Y.Offset)
ThumbsDownButton.Parent = VoteContainer
ThumbsUpImage.Parent = ThumbsUpButton
ThumbsDownImage.Parent = ThumbsDownButton
ThumbsUpImage.Position = UDim2.new(0.5, -ThumbsUpImage.Size.X.Offset / 2, 0.5, -ThumbsUpImage.Size.Y.Offset / 2)
ThumbsDownImage.Position = UDim2.new(0.5, -ThumbsDownImage.Size.X.Offset / 2, 0.5, -ThumbsDownImage.Size.Y.Offset / 2)
local RatingText = Utility.Create'TextLabel'
{
Name = "RatingText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, VoteContainer.Position.X.Offset + VoteContainer.Size.X.Offset / 2, 1, -20);
BackgroundTransparency = 1;
Font = GlobalSettings.BoldFont;
FontSize = GlobalSettings.SubHeaderSize;
Text = "";
Parent = this.Container;
}
local UpCountText = Utility.Create'TextLabel'
{
Name = "UpCountText";
Size = UDim2.new(0, 0, 0, 0);
Position = UDim2.new(0, 43, 1, RatingText.Position.Y.Offset);
BackgroundTransparency = 1;
Font = GlobalSettings.RegularFont;
FontSize = GlobalSettings.SubHeaderSize;
TextColor3 = GlobalSettings.GreenTextColor;
Text = "";
Parent = this.Container;
}
local DownCountText = UpCountText:Clone()
DownCountText.Name = "DownCountText"
DownCountText.Position = UDim2.new(0, 336, 1, RatingText.Position.Y.Offset)
DownCountText.TextColor3 = GlobalSettings.RedTextColor;
DownCountText.Parent = this.Container;
local function setImagesAndText(vote, noVote)
AssetManager.LocalImage(ThumbsUpImage,
(vote == true and 'rbxasset://textures/ui/Shell/Icons/ThumbsUpFilled') or
'rbxasset://textures/ui/Shell/Icons/ThumbsUp', {['720'] = UDim2.new(0,32,0,32); ['1080'] = UDim2.new(0,48,0,48);})
AssetManager.LocalImage(ThumbsDownImage,
(vote == false and 'rbxasset://textures/ui/Shell/Icons/ThumbsDownFilled') or
'rbxasset://textures/ui/Shell/Icons/ThumbsDown', {['720'] = UDim2.new(0,32,0,32); ['1080'] = UDim2.new(0,48,0,48);})
RatingText.TextColor3 =
(vote == true and GlobalSettings.GreenTextColor) or
(vote == false and GlobalSettings.RedTextColor) or GlobalSettings.GreyTextColor
local textWord = (noVote and "FirstToRateWord") or
(vote == true and "LikedWord") or (vote == false and "DislikedWord") or nil
RatingText.Text = textWord and Strings:LocalizedString(textWord) or ""
end
local function updateView(newVote)
local noVote = upVotes == 0 and downVotes == 0
VoteWidget:SetPercentFilled(not noVote and (upVotes / (upVotes + downVotes)) or nil)
setImagesAndText(newVote, noVote)
UpCountText.Text = Utility.FormatNumberString(noVote and "" or upVotes)
DownCountText.Text = Utility.FormatNumberString(noVote and "" or downVotes)
ThumbsUpButton.Selectable = canVote
ThumbsDownButton.Selectable = canVote
CannotVoteSelection.Parent = not canVote and this.Container.Parent or nil
defaultSelection = not canVote and CannotVoteSelection or ThumbsUpButton
end
local function updateVoteCount(prevVote, newVote)
-- we must check for a unique vote before sending off the achivement event
-- unique votes are when prevVote is null.
if prevVote == "null" then
UserData:IncrementVote()
elseif newVote == "null" then
UserData:DecrementVote()
end
EventHub:dispatchEvent(EventHub.Notifications["VotedOnPlace"])
end
local isVotingDebouce = false
local function postVoteAsync(newVote)
if isVotingDebouce then return end
isVotingDebouce = true
local success, reason = nil, nil
if thisGameData then
success, reason = thisGameData:PostVoteAsync(newVote)
else
success = false
end
if success then
local prevVote = myVote
if newVote == true then
upVotes = upVotes + 1
if prevVote == false then
downVotes = downVotes - 1
end
elseif newVote == false then
downVotes = downVotes + 1
if prevVote == true then
upVotes = upVotes - 1
end
elseif newVote == "null" then
if prevVote == true then
upVotes = upVotes - 1
elseif prevVote == false then
downVotes = downVotes - 1
end
end
myVote = newVote
updateView(newVote)
updateVoteCount(prevVote, newVote)
else
if reason and Errors.Vote[reason] then
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.Vote[reason]), false)
else
ScreenManager:OpenScreen(ErrorOverlayModule(Errors.Default), false)
end
end
isVotingDebouce = false
end
local function toggleCannotVoteDisplay()
CannotVoteText.Visible = not CannotVoteText.Visible
this.Container.Visible = not this.Container.Visible
end
CannotVoteSelection.SelectionGained:connect(toggleCannotVoteDisplay)
CannotVoteSelection.SelectionLost:connect(toggleCannotVoteDisplay)
ThumbsUpButton.MouseButton1Click:connect(function()
postVoteAsync(myVote == true and "null" or true)
end)
ThumbsDownButton.MouseButton1Click:connect(function()
postVoteAsync(myVote == false and "null" or false)
end)
--[[ Public API ]]--
function this:SetParent(newParent)
self.Container.Parent = newParent
end
function this:SetPosition(newPosition)
self.Container.Position = newPosition
CannotVoteSelection.Position = UDim2.new(0, 0, 0, self.Container.Position.Y.Offset + 5)
end
function this:SetVisible(value)
self.Container.Visible = value
end
function this:GetDefaultSelection()
return defaultSelection
end
function this:SetCanVote(value)
canVote = value
updateView(myVote)
end
function this:InitializeAsync(gameData)
thisGameData = gameData
local voteData = gameData:GetVoteDataAsync()
if voteData then
upVotes = voteData.UpVotes
downVotes = voteData.DownVotes
myVote = voteData.UserVote or "null"
canVote = voteData.CantVoteReason ~= "PlayGame"
else
myVote = "null"
end
updateView(myVote)
VoteWidget:SetVisible(true)
end
return this
end
return createVoteView
@@ -0,0 +1,7 @@
--[[
// Filename: ServerStarterScript.lua
// Version: 1.0
// Description: Server core script that handles core script server side logic.
// NOTE: This script does nothing right now for the sunshine app but it needed to not error.
]]--
@@ -0,0 +1,10 @@
local scriptContext = game:GetService("ScriptContext")
local rbxGui = Game:GetService("CoreGui"):FindFirstChild("RobloxGui")
scriptContext:AddCoreScriptLocal("CoreScripts/AppHome", rbxGui)
-- TODO: Clean this up so it doesn't get created anymore, but for now we should remove it
spawn(function()
local controlFrame = rbxGui:WaitForChild('ControlFrame')
controlFrame:Destroy()
end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,126 @@
{
"platform": "durango",
"atlas":
{
"pc": {
"width": 4096,
"height": 4096,
"tileSize": 512,
"tileCount": 6,
"borderSize": 85
},
"ios": {
"width": 2048,
"height": 2048,
"tileSize": 304,
"tileCount": 5,
"borderSize": 85
},
"android": {
"width": 2048,
"height": 2048,
"tileSize": 304,
"tileCount": 5,
"borderSize": 85
},
"durango": {
"width": 4096,
"height": 4096,
"tileSize": 512,
"tileCount": 6,
"borderSize": 85
}
},
"materials":
[
{
"name": "Air"
},
{
"name": "Water",
"water": 0.5
},
{
"name": "Grass",
"texture_top": { "tiling": 0.25, "detiling": 0.25 },
"texture_side": { "tiling": 0.2, "detiling": 0.2 },
"texture_bottom": { "tiling": 0.45, "detiling": 0.45 }
},
{
"name": "Slate",
"shift": 0.1,
"texture": { "tiling": 0.55, "detiling": 0.55 }
},
{
"name": "Concrete",
"quantize": 0.5,
"type": "hard",
"texture_top": { "tiling": 0.5, "detiling": 0.8 },
"texture_side": { "tiling": 0.4, "detiling": 0.4 }
},
{
"name": "Brick",
"cubify": 1,
"type": "hard",
"mapping": "cube",
"texture": { "tiling": 0.80, "detiling": 0.20 }
},
{
"name": "Sand",
"texture_top": { "tiling": 0.25, "detiling": 0.25 },
"texture_side": { "tiling": 0.25, "detiling": 0.25 }
},
{
"name": "WoodPlanks",
"cubify": 1.0,
"type": "hard",
"mapping": "cube",
"texture": { "tiling": 0.8, "detiling": 0.5 }
},
{
"name": "Rock",
"shift": 0.3,
"type": "hardsoft",
"texture": { "tiling": 0.55, "detiling": 0.55 }
},
{
"name": "Glacier",
"texture_top": { "tiling": 0.23, "detiling": 0.23 },
"texture_side": { "tiling": 0.2, "detiling": 0.9},
"texture_bottom": { "tiling": 0.23, "detiling": 0.6 }
},
{
"name": "Snow",
"texture": { "tiling": 0.3, "detiling": 0.3 }
},
{
"name": "Sandstone",
"texture_top": { "tiling": 0.5, "detiling": 0.7 },
"texture_side": { "tiling": 0.3, "detiling": 0.5003},
"texture_bottom": { "tiling": 0.45, "detiling": 0.45 }
},
{
"name": "Mud",
"texture": { "tiling": 0.3, "detiling": 0.1 }
},
{
"name": "Basalt",
"shift": 0.2,
"type": "hardsoft",
"texture": { "tiling": 0.55, "detiling": 0.55 }
},
{
"name": "Ground",
"texture": { "tiling": 0.3, "detiling": 0.3 }
},
{
"name": "CrackedLava",
"shift": 0.1,
"texture": { "tiling": 0.28, "detiling": 0.3 }
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More