761 lines
22 KiB
Ucode
761 lines
22 KiB
Ucode
|
/**
|
||
|
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* This class holds the set of playlists that the game exposes, handles
|
||
|
* downloading updates to the playlists via MCP/TitleFiles, and creates the
|
||
|
* game settings objects that make up a playlist
|
||
|
*/
|
||
|
class OnlinePlaylistManager extends Object
|
||
|
native
|
||
|
inherits(FTickableObject)
|
||
|
config(Playlist);
|
||
|
|
||
|
/** Contains a game settings class name to load and instance using the specified URL to override defaults */
|
||
|
struct native ConfiguredGameSetting
|
||
|
{
|
||
|
/** The unique (within the playlist) id for this game setting */
|
||
|
var int GameSettingId;
|
||
|
/** The name of the class to load and instance */
|
||
|
var string GameSettingsClassName;
|
||
|
/** The URL to use to replace settings with (see UpdateFromURL()) */
|
||
|
var string Url;
|
||
|
/** Holds the object that was created for this entry in a playlist */
|
||
|
var transient OnlineGameSettings GameSettings;
|
||
|
};
|
||
|
|
||
|
/** Allows for per playlist inventory swapping */
|
||
|
struct native InventorySwap
|
||
|
{
|
||
|
/** The inventory to replace */
|
||
|
var name Original;
|
||
|
/** The inventory to replace it with */
|
||
|
var string SwapTo;
|
||
|
};
|
||
|
|
||
|
// Defined match types
|
||
|
const PLAYER_MATCH = 0;
|
||
|
const RANKED_MATCH = 1;
|
||
|
const REC_MATCH = 2;
|
||
|
const PRIVATE_MATCH = 3;
|
||
|
|
||
|
/** A playlist contains 1 or more game configurations that players can choose between */
|
||
|
struct native Playlist
|
||
|
{
|
||
|
/** Holds the list of game configurations that are part of this playlist */
|
||
|
var array<ConfiguredGameSetting> ConfiguredGames;
|
||
|
/** The unique id for this playlist */
|
||
|
var int PlaylistId;
|
||
|
/** The load balance id for this playlist */
|
||
|
var int LoadBalanceId;
|
||
|
/** The string to use to lookup the display name for this playlist */
|
||
|
var string LocalizationString;
|
||
|
/** The set of content/maps (or DLC bundles) that must be present in order to play on this playlist */
|
||
|
var array<int> ContentIds;
|
||
|
/** The number of players per team if different from the defaults */
|
||
|
var int TeamSize;
|
||
|
/** The number of teams per match if different from the defaults */
|
||
|
var int TeamCount;
|
||
|
/** The max party size for this playlist if different from the team size */
|
||
|
var int MaxPartySize;
|
||
|
/** The string to use in the UI for this playlist */
|
||
|
var string Name;
|
||
|
/** URL to append to the MP options in GameInfo.InitGame() */
|
||
|
var string Url;
|
||
|
/** The type of match this is (ranked, player, recreational, whatever you want) */
|
||
|
var int MatchType;
|
||
|
/** Whether dedicated server searches are supported with this playlist */
|
||
|
var bool bDisableDedicatedServerSearches;
|
||
|
/** The custom map cycle for this playlist */
|
||
|
var array<name> MapCycle;
|
||
|
/** The list of inventory swaps for the playlist */
|
||
|
var array<InventorySwap> InventorySwaps;
|
||
|
};
|
||
|
|
||
|
/** This is the complete set of playlists available to choose from */
|
||
|
var config array<Playlist> Playlists;
|
||
|
|
||
|
/** The file names to request when downloading a playlist from MCP/TMS/etc */
|
||
|
var array<string> PlaylistFileNames;
|
||
|
|
||
|
/** The set of UIDataStore_GameResource objects to refresh once the download has completed */
|
||
|
var config array<name> DatastoresToRefresh;
|
||
|
|
||
|
/** Used to know when we should finalize the objects */
|
||
|
var int DownloadCount;
|
||
|
|
||
|
/** Incremented when successful to determine whether to update at all */
|
||
|
var int SuccessfulCount;
|
||
|
|
||
|
/** The version number of the playlist that was downloaded */
|
||
|
var config int VersionNumber;
|
||
|
|
||
|
/** Holds the overall and per region playlist population numbers */
|
||
|
struct native PlaylistPopulation
|
||
|
{
|
||
|
/** The unique id for this playlist */
|
||
|
var int PlaylistId;
|
||
|
/** The total across all regions */
|
||
|
var int WorldwideTotal;
|
||
|
/** The total for the player's region */
|
||
|
var int RegionTotal;
|
||
|
};
|
||
|
|
||
|
/** The list of playlists and the number of players in them */
|
||
|
var config array<PlaylistPopulation> PopulationData;
|
||
|
|
||
|
/** The total number of players across all playlists worldwide */
|
||
|
var int WorldwideTotalPlayers;
|
||
|
|
||
|
/** The total number of players across all playlists in the region */
|
||
|
var int RegionTotalPlayers;
|
||
|
|
||
|
/** Cached object ref that we use for accessing the TitleFileInterface */
|
||
|
var transient OnlineTitleFileInterface TitleFileInterface;
|
||
|
|
||
|
/** The name of the population data file to request */
|
||
|
var string PopulationFileName;
|
||
|
|
||
|
/** The next time the playlist population data needs to be sent */
|
||
|
var transient float NextPlaylistPopulationUpdateTime;
|
||
|
|
||
|
/** How often (in seconds) we should update the population data */
|
||
|
var config float PlaylistPopulationUpdateInterval;
|
||
|
|
||
|
/** The lowest number playlist id to report to the backend. Used to turn off "not mp" playlist ids */
|
||
|
var config int MinPlaylistIdToReport;
|
||
|
|
||
|
/** The playlist id that is being played */
|
||
|
var transient int CurrentPlaylistId;
|
||
|
|
||
|
/** The name of the interface to request as our upload object */
|
||
|
var config name EventsInterfaceName;
|
||
|
|
||
|
/** The datacenter id to use for this machine */
|
||
|
var config int DataCenterId;
|
||
|
|
||
|
/** The name of the datacenter file to request */
|
||
|
var string DataCenterFileName;
|
||
|
|
||
|
/** The time of the last download */
|
||
|
var transient float LastPlaylistDownloadTime;
|
||
|
|
||
|
/** How often to refresh the playlists */
|
||
|
var config float PlaylistRefreshInterval;
|
||
|
|
||
|
cpptext
|
||
|
{
|
||
|
// FTickableObject interface
|
||
|
|
||
|
/**
|
||
|
* Returns whether it is okay to tick this object. E.g. objects being loaded in the background shouldn't be ticked
|
||
|
* till they are finalized and unreachable objects cannot be ticked either.
|
||
|
*
|
||
|
* @return TRUE if tickable, FALSE otherwise
|
||
|
*/
|
||
|
virtual UBOOL IsTickable() const
|
||
|
{
|
||
|
// We cannot tick objects that are unreachable or are in the process of being loaded in the background.
|
||
|
return !HasAnyFlags(RF_Unreachable | RF_AsyncLoading);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used to determine if an object should be ticked when the game is paused.
|
||
|
*
|
||
|
* @return always TRUE as networking needs to be ticked even when paused
|
||
|
*/
|
||
|
virtual UBOOL IsTickableWhenPaused() const
|
||
|
{
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determines whether an update of the playlist population information is needed or not
|
||
|
*
|
||
|
* @param DeltaTime the amount of time that has passed since the last tick
|
||
|
*/
|
||
|
virtual void Tick(FLOAT DeltaTime);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delegate fired when the playlist has been downloaded and processed
|
||
|
*
|
||
|
* @param bWasSuccessful true if the playlist was read correctly, false if failed
|
||
|
*/
|
||
|
delegate OnReadPlaylistComplete(bool bWasSuccessful);
|
||
|
|
||
|
/**
|
||
|
* Reads the playlist from either MCP or from some form of title storage
|
||
|
*/
|
||
|
function DownloadPlaylist()
|
||
|
{
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
local int FileIndex;
|
||
|
|
||
|
// Force a reset of the files if we need an update
|
||
|
if (ShouldRefreshPlaylists())
|
||
|
{
|
||
|
Reset();
|
||
|
}
|
||
|
if (SuccessfulCount == 0)
|
||
|
{
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None &&
|
||
|
OnlineSub.Patcher != None)
|
||
|
{
|
||
|
// Request notification of file downloads
|
||
|
OnlineSub.Patcher.AddReadFileDelegate(OnReadTitleFileComplete);
|
||
|
// Reset our counts since we might be re-downloading
|
||
|
DownloadCount = 0;
|
||
|
SuccessfulCount = 0;
|
||
|
// Don't rebuild the list of files when already there
|
||
|
if (PlaylistFileNames.Length == 0)
|
||
|
{
|
||
|
DetermineFilesToDownload();
|
||
|
}
|
||
|
// Iterate the files that we read and request them
|
||
|
for (FileIndex = 0; FileIndex < PlaylistFileNames.Length; FileIndex++)
|
||
|
{
|
||
|
OnlineSub.Patcher.AddFileToDownload(PlaylistFileNames[FileIndex]);
|
||
|
}
|
||
|
// Don't read data center or force update the playlist population interval
|
||
|
// except on the first time we load the data
|
||
|
if (LastPlaylistDownloadTime == 0)
|
||
|
{
|
||
|
// Download the datacenter file now too
|
||
|
if (class'WorldInfo'.static.GetWorldInfo().NetMode != NM_DedicatedServer)
|
||
|
{
|
||
|
ReadDataCenterId();
|
||
|
}
|
||
|
// Force an update of the playlist population data
|
||
|
NextPlaylistPopulationUpdateTime = PlaylistPopulationUpdateInterval;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`Log("No online layer present, using defaults for playlist",,'DevMCP');
|
||
|
// Initialize all playlist objects with defaults
|
||
|
FinalizePlaylistObjects();
|
||
|
// Notify the completion
|
||
|
OnReadPlaylistComplete(true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Notify the completion
|
||
|
OnReadPlaylistComplete(SuccessfulCount == DownloadCount);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Uses the current loc setting and game ini name to build the download list */
|
||
|
native function DetermineFilesToDownload();
|
||
|
|
||
|
/** @return true if the playlists should be refreshed, false otherwise */
|
||
|
native function bool ShouldRefreshPlaylists();
|
||
|
|
||
|
/**
|
||
|
* Notifies us when the download of the playlist file is complete
|
||
|
*
|
||
|
* @param bWasSuccessful true if the download completed ok, false otherwise
|
||
|
* @param FileName the file that was downloaded (or failed to)
|
||
|
*/
|
||
|
function OnReadTitleFileComplete(bool bWasSuccessful,string FileName)
|
||
|
{
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
local int FileIndex;
|
||
|
|
||
|
for (FileIndex = 0; FileIndex < PlaylistFileNames.Length; FileIndex++)
|
||
|
{
|
||
|
if (PlaylistFileNames[FileIndex] == FileName)
|
||
|
{
|
||
|
// Increment how many we've downloaded
|
||
|
DownloadCount++;
|
||
|
SuccessfulCount += int(bWasSuccessful);
|
||
|
// If they have all been downloaded, rebuild the playlist
|
||
|
if (DownloadCount == PlaylistFileNames.Length)
|
||
|
{
|
||
|
if (SuccessfulCount != DownloadCount)
|
||
|
{
|
||
|
`Log("PlaylistManager: not all files downloaded correctly, using defaults where applicable",,'DevMCP');
|
||
|
}
|
||
|
// Rebuild the playlist and update any objects/ui
|
||
|
FinalizePlaylistObjects();
|
||
|
// Notify our requester
|
||
|
OnReadPlaylistComplete(SuccessfulCount == DownloadCount);
|
||
|
|
||
|
// Remove the delegates since we are done
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None &&
|
||
|
OnlineSub.Patcher != None)
|
||
|
{
|
||
|
OnlineSub.Patcher.ClearReadFileDelegate(OnReadTitleFileComplete);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Uses the configuration data to create the requested objects and then applies any
|
||
|
* specific game settings changes to them
|
||
|
*/
|
||
|
native function FinalizePlaylistObjects();
|
||
|
|
||
|
/**
|
||
|
* Finds the game settings object associated with this playlist and game settings id
|
||
|
*
|
||
|
* @param PlaylistId the playlist we are searching
|
||
|
* @param GameSettingsId the game settings id being searched for
|
||
|
*
|
||
|
* @return the game settings specified or None if not found
|
||
|
*/
|
||
|
function OnlineGameSettings GetGameSettings(int PlaylistId,int GameSettingsId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
local int GameIndex;
|
||
|
|
||
|
// Find the matching playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
// Search through the registered games for this playlist
|
||
|
for (GameIndex = 0; GameIndex < Playlists[PlaylistIndex].ConfiguredGames.Length; GameIndex++)
|
||
|
{
|
||
|
if (Playlists[PlaylistIndex].ConfiguredGames[GameIndex].GameSettingId == GameSettingsId)
|
||
|
{
|
||
|
return Playlists[PlaylistIndex].ConfiguredGames[GameIndex].GameSettings;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return None;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determine if any game settings exist for the given playlistid
|
||
|
*
|
||
|
* @param PlaylistId playlist to check for game settings
|
||
|
* @return TRUE if game settings exist for the given id
|
||
|
*/
|
||
|
function bool HasAnyGameSettings(int PlaylistId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
local int GameIndex;
|
||
|
|
||
|
// Find the matching playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
// Search through the registered games for this playlist
|
||
|
for (GameIndex = 0; GameIndex < Playlists[PlaylistIndex].ConfiguredGames.Length; GameIndex++)
|
||
|
{
|
||
|
if (Playlists[PlaylistIndex].ConfiguredGames[GameIndex].GameSettings != None)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Determines if this playlist can be found on dedicated servers
|
||
|
*
|
||
|
* @param PlaylistId playlist to check for dedicated server support
|
||
|
*/
|
||
|
function bool PlaylistSupportsDedicatedServers(int PlaylistId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
// Search through the registered games for this playlist
|
||
|
return !Playlists[PlaylistIndex].bDisableDedicatedServerSearches;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds the team information for the specified playlist and returns it in the out vars
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
* @param TeamSize out var getting the number of players per team
|
||
|
* @param TeamCount out var getting the number of teams per match
|
||
|
* @param MaxPartySize out var getting the number of players per party
|
||
|
*/
|
||
|
function GetTeamInfoFromPlaylist(int PlaylistId,out int TeamSize,out int TeamCount,out int MaxPartySize)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
TeamSize = 0;
|
||
|
TeamCount = 0;
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
TeamSize = Playlists[PlaylistIndex].TeamSize;
|
||
|
TeamCount = Playlists[PlaylistIndex].TeamCount;
|
||
|
MaxPartySize = Playlists[PlaylistIndex].MaxPartySize;
|
||
|
// If it wasn't set up for this playlist, use the team size
|
||
|
if (MaxPartySize == 0)
|
||
|
{
|
||
|
MaxPartySize = TeamSize;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determine the load balance id for the specified playlist and returns it in the out vars.
|
||
|
* The load balance id can be used to change the match mode during a search.
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
* @param LoadBalanceId out var getting the id for load balancing
|
||
|
*/
|
||
|
function GetLoadBalanceIdFromPlaylist(int PlaylistId,out int LoadBalanceId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
LoadBalanceId = 0;
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
LoadBalanceId = Playlists[PlaylistIndex].LoadBalanceId;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determine if the given playlist entry is arbitrated or not
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
*
|
||
|
* @return TRUE if the playlist is arbitrated
|
||
|
*/
|
||
|
function bool IsPlaylistArbitrated(int PlaylistId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
return Playlists[PlaylistIndex].MatchType == RANKED_MATCH;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Determine if the given playlist entry is arbitrated or not
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
*
|
||
|
* @return the match type for the playlist
|
||
|
*/
|
||
|
function int GetMatchType(int PlaylistId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
return Playlists[PlaylistIndex].MatchType;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds the specified playlist and returns any additional URL options
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
*
|
||
|
* @return the URL string associated with this playlist
|
||
|
*/
|
||
|
function string GetUrlFromPlaylist(int PlaylistId)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
return Playlists[PlaylistIndex].Url;
|
||
|
}
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds the specified playlist and returns the map cycle for it
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
* @param MapCycle the out var that gets the map cycle
|
||
|
*/
|
||
|
function GetMapCycleFromPlaylist(int PlaylistId,out array<name> MapCycle)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
// Don't overwrite if there isn't one specified
|
||
|
if (Playlists[PlaylistIndex].MapCycle.Length > 0)
|
||
|
{
|
||
|
MapCycle = Playlists[PlaylistIndex].MapCycle;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Searches for a per playlist inventory swap
|
||
|
*
|
||
|
* @param PlaylistId the playlist we are checking for swaps in
|
||
|
* @param SourceInventory the source item we are checking for swapping
|
||
|
*
|
||
|
* @return the swapped item or the original if not found
|
||
|
*/
|
||
|
function class<Inventory> GetInventorySwapFromPlaylist(int PlaylistId,class<Inventory> SourceInventory)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
local int SwapIndex;
|
||
|
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
SwapIndex = Playlists[PlaylistIndex].InventorySwaps.Find('Original',SourceInventory.Name);
|
||
|
if (SwapIndex != INDEX_NONE)
|
||
|
{
|
||
|
return class<Inventory>(DynamicLoadObject(Playlists[PlaylistIndex].InventorySwaps[SwapIndex].SwapTo,class'class'));
|
||
|
}
|
||
|
}
|
||
|
return SourceInventory;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds the specified playlist and return the content ids in the out var
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
* @param ContentIds the list to set the content ids in
|
||
|
*/
|
||
|
function GetContentIdsFromPlaylist(int PlaylistId,out array<int> ContentIds)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
// Find the matching playlist
|
||
|
PlaylistIndex = Playlists.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
ContentIds = Playlists[PlaylistIndex].ContentIds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Allows the playlists to be re-requested from the server
|
||
|
*/
|
||
|
function Reset()
|
||
|
{
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
|
||
|
DownloadCount = 0;
|
||
|
SuccessfulCount = 0;
|
||
|
|
||
|
// Clear out any cached file contents if present
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None &&
|
||
|
OnlineSub.Patcher != None)
|
||
|
{
|
||
|
OnlineSub.Patcher.ClearCachedFiles();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Reads the player population data for playlists by region
|
||
|
*/
|
||
|
function ReadPlaylistPopulation()
|
||
|
{
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
|
||
|
// Get the object to download with the first time
|
||
|
if (TitleFileInterface == None)
|
||
|
{
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None)
|
||
|
{
|
||
|
// Ask for the interface by name
|
||
|
TitleFileInterface = OnlineSub.TitleFileInterface;
|
||
|
}
|
||
|
}
|
||
|
if (TitleFileInterface != None)
|
||
|
{
|
||
|
// Force an update of this information
|
||
|
TitleFileInterface.ClearDownloadedFile(PopulationFileName);
|
||
|
// Set the callback for notifications of files completing
|
||
|
TitleFileInterface.AddReadTitleFileCompleteDelegate(OnReadPlaylistPopulationComplete);
|
||
|
// Request the playlist population numbers
|
||
|
TitleFileInterface.ReadTitleFile(PopulationFileName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`warn("Cannot download playlist population due to missing TitleFileInterface object");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Notifies us when the download of a file is complete
|
||
|
*
|
||
|
* @param bWasSuccessful true if the download completed ok, false otherwise
|
||
|
* @param FileName the file that was downloaded (or failed to)
|
||
|
*/
|
||
|
function OnReadPlaylistPopulationComplete(bool bWasSuccessful,string FileName)
|
||
|
{
|
||
|
local array<byte> FileData;
|
||
|
|
||
|
if (FileName == PopulationFileName)
|
||
|
{
|
||
|
if (bWasSuccessful)
|
||
|
{
|
||
|
// Read the contents so that they can be processed
|
||
|
if (TitleFileInterface.GetTitleFileContents(FileName,FileData))
|
||
|
{
|
||
|
ParsePlaylistPopulationData(FileData);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`Log("Failed to download the file ("$FileName$") from TitleFileInterface",,'DevMCP');
|
||
|
}
|
||
|
// Notify any listener
|
||
|
OnPlaylistPopulationDataUpdated();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Tells the listener when playlist population data was updated
|
||
|
*/
|
||
|
delegate OnPlaylistPopulationDataUpdated();
|
||
|
|
||
|
/**
|
||
|
* Converts the data into the structure used by the playlist manager
|
||
|
*
|
||
|
* @param Data the data that was downloaded
|
||
|
*/
|
||
|
native function ParsePlaylistPopulationData(const out array<byte> Data);
|
||
|
|
||
|
/**
|
||
|
* Finds the population information for the specified playlist and returns it in the out vars
|
||
|
*
|
||
|
* @param PlaylistId the playlist being searched for
|
||
|
* @param WorldwideTotal out var getting the number of players worldwide
|
||
|
* @param RegionTotal out var getting the number of players in this region
|
||
|
*/
|
||
|
function GetPopulationInfoFromPlaylist(int PlaylistId,out int WorldwideTotal,out int RegionTotal)
|
||
|
{
|
||
|
local int PlaylistIndex;
|
||
|
|
||
|
WorldwideTotal = 0;
|
||
|
RegionTotal = 0;
|
||
|
// Find the playlist
|
||
|
PlaylistIndex = PopulationData.Find('PlaylistId',PlaylistId);
|
||
|
if (PlaylistIndex != INDEX_NONE)
|
||
|
{
|
||
|
WorldwideTotal = PopulationData[PlaylistIndex].WorldwideTotal;
|
||
|
RegionTotal = PopulationData[PlaylistIndex].RegionTotal;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called once enough time has elapsed that a playlist update is required
|
||
|
*
|
||
|
* @param NumPlayers the numbers of players to report (easier to get at in native)
|
||
|
*/
|
||
|
event SendPlaylistPopulationUpdate(int NumPlayers)
|
||
|
{
|
||
|
local OnlineEventsInterface EventsInterface;
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None)
|
||
|
{
|
||
|
EventsInterface = OnlineEventsInterface(OnlineSub.GetNamedInterface(EventsInterfaceName));
|
||
|
// Always send population data if requested (dedicated might have NumPlayers=0 but we want the heartbeat)
|
||
|
if (EventsInterface != None)
|
||
|
{
|
||
|
`Log("Updating playlist population with PlaylistId="$CurrentPlaylistId$" and NumPlayers="$NumPlayers,,'DevMCP');
|
||
|
// Send this to the network backend
|
||
|
EventsInterface.UpdatePlaylistPopulation(CurrentPlaylistId,NumPlayers);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Asks the network backend which datacenter this machine is to use
|
||
|
*/
|
||
|
function ReadDataCenterId()
|
||
|
{
|
||
|
local OnlineSubsystem OnlineSub;
|
||
|
|
||
|
// Get the object to download with the first time
|
||
|
if (TitleFileInterface == None)
|
||
|
{
|
||
|
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
|
||
|
if (OnlineSub != None)
|
||
|
{
|
||
|
// Ask for the interface by name
|
||
|
TitleFileInterface = OnlineSub.TitleFileInterface;
|
||
|
}
|
||
|
}
|
||
|
if (TitleFileInterface != None)
|
||
|
{
|
||
|
// Set the callback for notifications of files completing
|
||
|
TitleFileInterface.AddReadTitleFileCompleteDelegate(OnReadDataCenterIdComplete);
|
||
|
// Request the datacenter id
|
||
|
TitleFileInterface.ReadTitleFile(DataCenterFileName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`warn("Cannot download datacenter id due to missing TitleFileInterface object");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Notifies us when the download of a file is complete
|
||
|
*
|
||
|
* @param bWasSuccessful true if the download completed ok, false otherwise
|
||
|
* @param FileName the file that was downloaded (or failed to)
|
||
|
*/
|
||
|
function OnReadDataCenterIdComplete(bool bWasSuccessful,string FileName)
|
||
|
{
|
||
|
local array<byte> FileData;
|
||
|
|
||
|
if (bWasSuccessful)
|
||
|
{
|
||
|
if (FileName == DataCenterFileName)
|
||
|
{
|
||
|
// Read the contents so that they can be processed
|
||
|
if (TitleFileInterface.GetTitleFileContents(FileName,FileData))
|
||
|
{
|
||
|
ParseDataCenterId(FileData);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
`Log("Failed to download the file ("$FileName$") from TitleFileInterface",,'DevMCP');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts the data into the datacenter id
|
||
|
*
|
||
|
* @param Data the data that was downloaded
|
||
|
*/
|
||
|
native function ParseDataCenterId(const out array<byte> Data);
|
||
|
|
||
|
defaultproperties
|
||
|
{
|
||
|
PopulationFileName="PlaylistPopulationData.ini"
|
||
|
DataCenterFileName="DataCenter.Id"
|
||
|
}
|