1
0
This commit is contained in:
2020-12-13 18:01:13 +03:00
commit dd42f84140
3764 changed files with 596895 additions and 0 deletions

View File

@ -0,0 +1,53 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Allows a client to register and resolve the address for a host that it wants to connect to.
* The platform specific implementation for this resolver will handle the specifics of generating
* a secure key to allow for a connection.
*/
class ClientBeaconAddressResolver extends Object
native
config(Engine);
/** The port that the beacon will listen on */
var int BeaconPort;
/** The name to use when logging (helps debugging) */
var name BeaconName;
cpptext
{
/**
* Performs platform specific resolution of the address
*
* @param DesiredHost the host to resolve the IP address for
* @param Addr out param having it's address set
*
* @return true if the address could be resolved, false otherwise
*/
virtual UBOOL ResolveAddress(const FOnlineGameSearchResult& DesiredHost,FInternetIpAddr& Addr);
/**
* Allows for per platform registration of secure keys, so that a secure connection
* can be opened and used for sending/receiving data.
*
* @param DesiredHost the host that is being registered
*/
virtual UBOOL RegisterAddress(const FOnlineGameSearchResult& DesiredHost)
{
return TRUE;
}
/**
* Allows for per platform unregistration of secure keys, which breaks the link between
* a client and server. This also releases any memory associated with the keys.
*
* @param DesiredHost the host that is being registered
*/
virtual UBOOL UnregisterAddress(const FOnlineGameSearchResult& DesiredHost)
{
return TRUE;
}
}

73
IpDrv/Classes/HelloWeb.uc Normal file
View File

@ -0,0 +1,73 @@
/*=============================================================================
HelloWeb.uc - example web server
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class HelloWeb extends WebApplication;
/* Usage:
This is a sample web application, to demonstrate how to program for the web server.
[IpDrv.WebServer]
Applications[0]="IpDrv.HelloWeb"
ApplicationPaths[0]="/hello"
bEnabled=True
http://server.ip.address/hello
*/
function Init()
{
`log("HelloWeb INIT");
}
event Query(WebRequest Request, WebResponse Response)
{
local int i;
`log("Query received"@Request@Response);
if(Request.Username != "test" || Request.Password != "test")
{
Response.FailAuthentication("HelloWeb");
return;
}
switch(Request.URI)
{
case "/form.html":
Response.SendText("<form method=post action=submit.html>");
Response.SendText("<input type=edit name=TestEdit>");
Response.SendText("<p><select multiple name=selecter>");
Response.SendText("<option value=\"one\">Number One");
Response.SendText("<option value=\"two\">Number Two");
Response.SendText("<option value=\"three\">Number Three");
Response.SendText("<option value=\"four\">Number Four");
Response.SendText("</select><p>");
Response.SendText("<input type=submit name=Submit value=Submit>");
Response.SendText("</form>");
break;
case "/submit.html":
Response.SendText("Thanks for submitting the form.<br>");
Response.SendText("TestEdit was \""$Request.GetVariable("TestEdit")$"\"<p>");
Response.SendText("You selected these items:<br>");
for(i=Request.GetVariableCount("selecter")-1;i>=0;i--)
Response.SendText("\""$Request.GetVariableNumber("selecter", i)$"\"<br>");
break;
case "/include.html":
Response.Subst("variable1", "This is variable 1");
Response.Subst("variable2", "This is variable 2");
Response.Subst("variable3", "This is variable 3");
Response.IncludeUHTM("testinclude.html");
break;
default:
Response.SendText("Hello web! The current level is "$WorldInfo.Title);
Response.SendText("<br>Click <a href=\"form.html\">this link</a> to go to a test form");
break;
}
}
defaultproperties
{
}

View File

@ -0,0 +1,48 @@
/*=============================================================================
ImageServer.uc - example image server
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class ImageServer extends WebApplication;
/* Usage:
[IpDrv.WebServer]
Applications[0]="IpDrv.ImageServer"
ApplicationPaths[0]="/images"
bEnabled=True
http://server.ip.address/images/test.jpg
*/
event Query(WebRequest Request, WebResponse Response)
{
local string Image;
Image = Request.URI;
if (!Response.FileExists(Path $ Image))
{
Response.HTTPError(404);
return;
}
else if( Right(Caps(Image), 4) == ".JPG" || Right(Caps(Image), 5) == ".JPEG" )
{
Response.SendStandardHeaders("image/jpeg", true);
}
else if( Right(Caps(Image), 4) == ".GIF" )
{
Response.SendStandardHeaders("image/gif", true);
}
else if( Right(Caps(Image), 4) == ".BMP" )
{
Response.SendStandardHeaders("image/bmp", true);
}
else if( Right(Caps(Image), 4) == ".PNG" )
{
Response.SendStandardHeaders("image/png", true);
}
else
{
Response.SendStandardHeaders("application/octet-stream", true);
}
Response.IncludeBinaryFile( Path $ Image );
}

View File

@ -0,0 +1,113 @@
/*=============================================================================
// InternetLink: Parent class for Internet connection classes
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class InternetLink extends Info
native
transient;
cpptext
{
AInternetLink();
void BeginDestroy();
UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
FSocket* GetSocket()
{
return Socket;
}
FSocket* GetRemoteSocket()
{
return RemoteSocket;
}
FResolveInfo*& GetResolveInfo()
{
return *(FResolveInfo**)&PrivateResolveInfo;
}
}
//-----------------------------------------------------------------------------
// Types & Variables.
// Data receive mode.
// Cannot be set in default properties.
var enum ELinkMode
{
MODE_Text,
MODE_Line,
MODE_Binary
} LinkMode;
// MODE_Line behavior, how to receive/send lines
var enum ELineMode
{
LMODE_auto,
LMODE_DOS, // CRLF
LMODE_UNIX, // LF
LMODE_MAC, // LFCR
} InLineMode, OutLineMode; // OutLineMode: LMODE_auto == LMODE_DOS
// Internal
var const pointer Socket{FSocket}; // (sockets are 64-bit on AMD64, so use "pointer").
var const int Port;
var const pointer RemoteSocket{FSocket};
var private native const pointer PrivateResolveInfo;
var const int DataPending;
// Receive mode.
// If mode is MODE_Manual, received events will not be called.
// This means it is your responsibility to check the DataPending
// var and receive the data.
// Cannot be set in default properties.
var enum EReceiveMode
{
RMODE_Manual,
RMODE_Event
} ReceiveMode;
//-----------------------------------------------------------------------------
// Natives.
// Returns true if data is pending on the socket.
native function bool IsDataPending();
// Parses an Unreal URL into its component elements.
// Returns false if the URL was invalid.
native function bool ParseURL
(
coerce string URL,
out string Addr,
out int PortNum,
out string LevelName,
out string EntryName
);
// Resolve a domain or dotted IP.
// Nonblocking operation.
// Triggers Resolved event if successful.
// Triggers ResolveFailed event if unsuccessful.
native function Resolve( coerce string Domain );
// Returns most recent winsock error.
native function int GetLastError();
// Convert an IP address to a string.
native function string IpAddrToString( IpAddr Arg );
// Convert a string to an IP
native function bool StringToIpAddr( string Str, out IpAddr Addr );
native function GetLocalIP(out IpAddr Arg );
//-----------------------------------------------------------------------------
// Events.
// Called when domain resolution is successful.
// The IpAddr struct Addr contains the valid address.
event Resolved( IpAddr Addr );
// Called when domain resolution fails.
event ResolveFailed();
defaultproperties
{
}

49
IpDrv/Classes/MCPBase.uc Normal file
View File

@ -0,0 +1,49 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Provides a base class for commonly needed MCP functions
*/
class MCPBase extends McpServiceBase
native
abstract
inherits(FTickableObject)
config(Engine);
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;
}
/**
* Needs to be overridden by child classes
*
* @param ignored
*/
virtual void Tick(FLOAT)
{
check(0 && "Implement this in child classes");
}
}

View File

@ -0,0 +1,295 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for ClashMob Mcp services
*/
class McpClashMobBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config String McpClashMobClassName;
enum McpChallengeFileStatus
{
/** No action yet */
MCFS_NotStarted,
/** Cache load or web download pending */
MCFS_Pending,
/** Data was successfully loaded */
MCFS_Success,
/** Data was not successfully loaded */
MCFS_Failed
};
/** Single file entry that can be downloaded for a challenge */
struct McpClashMobChallengeFile
{
// JSON imported properties
var bool should_keep_post_challenge;
var string title_id;
var string file_name;
var string dl_name;
var string hash_code;
var string type;
/** Status of file load/download */
var McpChallengeFileStatus Status;
};
/** Parameters of a push notification */
struct McpClashMobPushNotificationParams
{
// JSON imported properties
var int bah;
};
/* Info about notifications that will be triggered for a challenge */
struct McpClashMobPushNotification
{
// JSON imported properties
var array<string> device_tokens;
var string badge_type;
var string sound;
var string message;
var McpClashMobPushNotificationParams params;
};
/** Single challenge event that is queried from the server */
struct McpClashMobChallengeEvent
{
// JSON imported properties
var string unique_challenge_id;
var string visible_date;
var string start_date;
var string end_date;
var string completed_date;
var string purge_date;
var string challenge_type;
var int num_attempts;
var int num_successful_attempts;
var int goal_value;
var int goal_start_value;
var int goal_current_value;
var bool has_started;
var bool is_visible;
var bool has_completed;
var bool was_successful;
var array<McpClashMobChallengeFile> file_list;
var int facebook_likes;
var int facebook_comments;
var float facebook_like_scaler;
var float facebook_comment_scaler;
var int facebook_like_goal_progress;
var int facebook_comment_goal_progress;
var string facebook_id;
var int twitter_retweets;
var float twitter_retweets_scaler;
var int twitter_goal_progress;
var string twitter_id;
};
/** Current user status with respect to a single challenge event that is queried from the server */
struct McpClashMobChallengeUserStatus
{
// JSON imported properties
var string unique_challenge_id;
var string unique_user_id;
var int num_attempts;
var int num_successful_attempts;
var int goal_progress;
var bool did_complete;
var string last_update_time;
var int user_award_given;
var string accept_time;
var bool did_preregister;
var string facebook_like_time;
var bool enrolled_via_facebook;
var bool liked_via_facebook;
var bool commented_via_facebook;
var string twitter_retweet_time;
var bool enrolled_via_twitter;
var bool retweeted;
};
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpClashMobBase CreateInstance()
{
local class<McpClashMobBase> McpClashMobBaseClass;
local McpClashMobBase NewInstance;
McpClashMobBaseClass = class<McpClashMobBase>(DynamicLoadObject(default.McpClashMobClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpClashMobBaseClass != None)
{
NewInstance = new McpClashMobBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Delegate called when the challenge list query has completed
*
* @param bWasSuccessful true if the challenge list was retrieved from the server
* @param Error info string about why the error occurred
*/
delegate OnQueryChallengeListComplete(bool bWasSuccessful, String Error);
/**
* Initiates a web request to retrieve the list of available challenge events from the server.
*/
function QueryChallengeList();
/**
* Access the currently cached challenge list that was downloaded. Use QueryChallengeList first
*
* @param OutChallengeEvents the list of events that should be filled in
*/
function GetChallengeList(out array<McpClashMobChallengeEvent> OutChallengeEvents);
/**
* Delegate called when a single challenge file is loaded/downloaded
*
* @param bWasSuccessful true if the file was loaded from cache or downloaded from server
* @param UniqueChallengeId id of challenge that owns the file
* @param DlName download name of the file
* @param FileName logical name of the file
* @param Error info string about why the error occurred
*/
delegate OnDownloadChallengeFileComplete(bool bWasSuccessful, string UniqueChallengeId, string DlName, string FileName, string Error);
/**
* Get the list of files for a given challenge
*
* @param UniqueChallengeId id of challenge that may have files
* @param OutChallengeFiles list of files that should be filled in
*/
function GetChallengeFileList(string UniqueChallengeId, out array<McpClashMobChallengeFile> OutChallengeFiles);
/**
* Starts the load/download of a challenge file
*
* @param UniqueChallengeId id of challenge that owns the file
* @param DlName download name of the file
*/
function DownloadChallengeFile(string UniqueChallengeId, string DlName);
/**
* Access the cached copy of the file data
*
* @param UniqueChallengeId id of challenge that owns the file
* @param DlName download name of the file
* @param OutFileContents byte array filled in with the file contents
*/
function GetChallengeFileContents(string UniqueChallengeId, string DlName, out array<byte> OutFileContents);
/**
* Clear the cached memory copy of a challenge file
*
* @param UniqueChallengeId id of challenge that owns the file
* @param DlName download name of the file
*/
function ClearCachedChallengeFile(string UniqueChallengeId, string DlName);
/**
* Clear the cached memory copy of a challenge file
*
* @param UniqueChallengeId id of challenge that owns the file
* @param DlName download name of the file
*/
function DeleteCachedChallengeFile(string UniqueChallengeId, string DlName);
/**
* Called when the web request to have user accept a challenge completes
*
* @param bWasSuccessful true if request completed successfully
* @param UniqueChallengeId id of challenge to accept
* @param UniqueUserId id of user that wants to accept challenge
* @param Error info string about why the error occurred
*/
delegate OnAcceptChallengeComplete(bool bWasSuccessful, string UniqueChallengeId, string UniqueUserId, String Error);
/**
* Initiates a web request to have user accept a challenge
*
* @param UniqueChallengeId id of challenge to accept
* @param UniqueUserId id of user that wants to accept challenge
*/
function AcceptChallenge(string UniqueChallengeId, string UniqueUserId);
/**
* Called when the web request to retrieve the current status of a challenge for a user completes
*
* @param bWasSuccessful true if request completed successfully
* @param UniqueChallengeId id of challenge to query
* @param UniqueUserId id of user to retrieve challenge status for
* @param Error info string about why the error occurred
*/
delegate OnQueryChallengeUserStatusComplete(bool bWasSuccessful, string UniqueChallengeId, string UniqueUserId, String Error);
/**
* Initiates a web request to retrieve the current status of a challenge for a user
*
* @param UniqueChallengeId id of challenge to query
* @param UniqueUserId id of user to retrieve challenge status for
*/
function QueryChallengeUserStatus(string UniqueChallengeId, string UniqueUserId);
/**
* Initiates a web request to retrieve the current status of a challenge for a list of users user
*
* @param UniqueChallengeId id of challenge to query
* @param UniqueUserId id of user that is initiating the request
* @param UserIdsToRead list of ids to read status for
*/
function QueryChallengeMultiUserStatus(string UniqueChallengeId, string UniqueUserId, const out array<string> UserIdsToRead);
/**
* Get the cached status of a user for a challenge. Use QueryChallengeUserStatus first
*
* @param UniqueChallengeId id of challenge to retrieve
* @param UniqueUserId id of user to retrieve challenge status for
* @param OutChallengeUserStatus user status values to be filled in
*/
function GetChallengeUserStatus(string UniqueChallengeId, string UniqueUserId, out McpClashMobChallengeUserStatus OutChallengeUserStatus);
/**
* Called when the web request to update the current progress of a challenge for a user completes
*
* @param bWasSuccessful true if request completed successfully
* @param UniqueChallengeId id of challenge to update
* @param UniqueUserId id of user to update challenge progress for
* @param Error info string about why the error occurred
*/
delegate OnUpdateChallengeUserProgressComplete(bool bWasSuccessful, string UniqueChallengeId, string UniqueUserId, String Error);
/**
* Initiates a web request to update the current progress of a challenge for a user
*
* @param UniqueChallengeId id of challenge to update
* @param UniqueUserId id of user to update challenge progress for
*/
function UpdateChallengeUserProgress(string UniqueChallengeId, string UniqueUserId, bool bDidComplete, int GoalProgress);
/**
* Called when the web request to update the current reward of a challenge for a user completes
*
* @param bWasSuccessful true if request completed successfully
* @param UniqueChallengeId id of challenge to update
* @param UniqueUserId id of user to update challenge progress for
* @param Error info string about why the error occurred
*/
delegate OnUpdateChallengeUserRewardComplete(bool bWasSuccessful, string UniqueChallengeId, string UniqueUserId, String Error);
/**
* Initiates a web request to update the current reward given to a user for a challenge
*
* @param UniqueChallengeId id of challenge to update
* @param UniqueUserId id of user to update challenge progress for
*/
function UpdateChallengeUserReward(string UniqueChallengeId, string UniqueUserId, int UserReward);

View File

@ -0,0 +1,25 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for ClashMob Mcp services
*/
class McpClashMobFileDownload extends OnlineTitleFileDownloadWeb
config(Engine);
/**
* Build the clashmob specific Url for downloading a given file
*
* @param FileName the file to search the table for
*
* @param the URL to use to request the file or BaseURL if no special mapping is present
*/
function string GetUrlForFile(string FileName)
{
local string Url;
Url = GetBaseURL() $ RequestFileURL $
"?appKey=" $ McpConfig.AppKey $ "&appSecret=" $ McpConfig.AppSecret $
"&dlName=";
return Url;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,298 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for user groups and the factory method
* for creating the registered implementing object
*/
class McpGroupsBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config string McpGroupsManagerClassName;
/**
* Enum for whether or not a member has accepted a group invite
*/
enum EMcpGroupAcceptState
{
/** Member has rejected the invite to the group, or there has been an error. */
MGAS_Error,
/** Member has not yet responded to the group invite. */
MGAS_Pending,
/** Member has accepted the group invite. */
MGAS_Accepted
};
/**
* Enum for the Access Level of the group
*/
enum EMcpGroupAccessLevel
{
/** Only Owners have permission to make changes to this group */
MGAL_Owner,
/** Members and Owners have permission to make changes to this group */
MGAL_Member,
/** Everyone has permission to make changes to this group */
MGAL_Public
};
/**
* Group Member
*/
struct McpGroupMember
{
/** Member Id */
var String MemberId;
/** Member's Accept State to the group */
var EMcpGroupAcceptState AcceptState;
};
/**
* Group
*/
struct McpGroup
{
/** The User Id of the owner of the group */
var String OwnerId;
/** Unique Group Id */
var String GroupId;
/** Human Readable Name of the group.*/
var String GroupName;
/**
* Access level defining who can make modifications to the group.
* Note: Access is not restricted on the server, it is up to those using the API to enforce these permissions.
*/
var EMcpGroupAccessLevel AccessLevel;
/** Collection of users who are members of the group */
var array<McpGroupMember> Members;
};
/**
* List of Groups belonging to one user
*/
struct McpGroupList
{
/** User that requested the list of groups */
var string RequesterId;
/** Collection of groups that the user owns OR belongs to */
var array<McpGroup> Groups;
};
/** Holds the groups for each user in memory */
var array<McpGroupList> GroupLists;
/**
* Create Instance of an McpGroup
* @return the object that implements this interface or none if missing or failed to create/load
*/
static final function McpGroupsBase CreateInstance()
{
local class<McpGroupsBase> McpGroupsManagerClass;
local McpGroupsBase NewInstance;
McpGroupsManagerClass = class<McpGroupsBase>(DynamicLoadObject(default.McpGroupsManagerClassName,class'Class'));
if (McpGroupsManagerClass != None)
{
NewInstance = new McpGroupsManagerClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Creates the URL and sends the request to create a group.
*
* @param UniqueUserId the UserId that will own the group
* @param GroupName the name the group will be created with
*/
function CreateGroup(String OwnerId, String GroupName);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param GroupName the name of the group
* @param GroupId the group id of the group that was created
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnCreateGroupComplete(McpGroup Group, bool bWasSuccessful, String Error);
/**
* Deletes a Group by GroupId
*
* @param UniqueUserId UserId of the owner of the Group
* @param GroupId Id of the group
*/
function DeleteGroup(String UniqueUserId, String GroupId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param GroupId the group id of the group that was Deleted
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteGroupComplete(String GroupId, bool bWasSuccessful, String Error);
/**
* Queries the backend for the Groups belonging to the supplied UserId
*
* @param UniqueUserId the id of the owner of the groups to return
*/
function QueryGroups(String RequesterId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param UserId the user id of the groups that were queried
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryGroupsComplete(string UserId, bool bWasSuccessful, String Error);
/**
* Returns the set of groups that belonging to the specified UserId
* Called after QueryGroups.
*
* @param UserId the request object that was used
* @param GroupList the response object that was generated
*/
function GetGroupList(string UserId, out McpGroupList GroupList);
/**
* Queries the back-end for the Groups Members belonging to the specified group
*
* @param UniqueUserId the id of the owner of the group being queried
* @param GroupId the id of the owner of the groups to return
*/
function QueryGroupMembers(String UniqueUserId, String GroupId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param GroupId the id of the group from which the members were queried
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryGroupMembersComplete(String GroupId, bool bWasSuccessful, String Error);
/**
* Returns the set of Group Members that belong to the specified GroupId
* Called after QueryGroupMembers.
*
* @param GroupId the request object that was used
* @param GroupList the response object that was generated
*/
function GetGroupMembers(String GroupId, out array<McpGroupMember> GroupMembers);
/**
* Adds an Group Members to the specified Group. Sends this request to MCP to be processed
*
* @param UniqueUserId the user that owns the group
* @param GroupId the group id
* @param MemberIds list of member ids to add to the group
* @param bRequiresAcceptance whether or not members need to accept an invitation to a group
*/
function AddGroupMembers(String OwnerId, String GroupId, const out array<String> MemberIds, bool bRequiresAcceptance);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param GroupId the id of the group to which the members were added
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnAddGroupMembersComplete(String GroupId, bool bWasSuccessful, String Error);
/**
* Remove Group Members from the specified Group. Sends this request to MCP to be processed
*
* @param UniqueUserId the user that owns the group
* @param GroupId the group id
* @param MemberIds list of member ids to add to the group
*/
function RemoveGroupMembers(String OwnerId, String GroupId, const out array<String> MemberIds);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param GroupId the id of the group from which the members were removed
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnRemoveGroupMembersComplete(String GroupId, bool bWasSuccessful, String Error);
/**
* Deletes all Groups that belong to UniqueUserId
*
* @param UniqueUserId UserId of the owner of the Group
*/
function DeleteAllGroups(String OwnerId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteAllGroupsComplete(String RequesterId, bool bWasSuccessful, String Error);
/**
* Queries the backend for the Pending Group Invites belonging to the supplied UserId
*
* @param UniqueUserId the id of the user to query invites for
*/
function QueryGroupInvites(String UniqueUserId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryGroupInvitesComplete(bool bWasSuccessful, String Error);
/**
* Returns the set of Pending Group Invites that belong to the specified UserId
* Called after QueryGroupInvites.
*
* @param UserId the request object that was used
* @param GroupList the response object that was generated
*/
function GetGroupInviteList(string UserId, out McpGroupList InviteList);
/**
* Set's the a Member's membership status to Accept or Reject
* based on the value of bShouldAccept
*
* @param UniqueUserId User who's status is to be update
* @param GroupId
* @param bShouldAccept 1 = accepted 0 = rejected
*/
function AcceptGroupInvite(String UniqueUserId, String GroupId, bool bShouldAccept);
/**
* Called once the request/response has completed. Used to process the add mapping result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
delegate OnAcceptGroupInviteComplete(String GroupId, bool bWasSuccessful, String Error);

View File

@ -0,0 +1,943 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Concrete implementation for managing groups through the groups web API
*/
class McpGroupsManager extends McpGroupsBase
config(Engine);
`include(Engine\Classes\HttpStatusCodes.uci)
/** The URL of CreateGroup function on the server */
var config string CreateGroupUrl;
/** The URL of DeleteGroup function on the server */
var config string DeleteGroupUrl;
/** The URL of ListGroups function on the server */
var config string QueryGroupsUrl;
/** The URL of ListGroupMembers function on the server */
var config string QueryGroupMembersUrl;
/** The URL of AddGroupMembers function on the server */
var config string AddGroupMembersUrl;
/** The URL of RemoveGroupMembers function on the server */
var config string RemoveGroupMembersUrl;
/** The URL of DeleteAllGroups function on the server */
var config string DeleteAllGroupsUrl;
/** The URL of AcceptGroupInvite function on the server */
var config string AcceptGroupInviteUrl;
/** The URL of RejectGroupInvite function on the server */
var config string RejectGroupInviteUrl;
/**
* Creates the URL and sends the request to create a group.
* - This updates the group on the server QueryGroups will need to be
* - run again before GetGroups will reflect the this new group.
* @param UniqueUserId the UserId that will own the group
* @param GroupName The name the group will be created with
*/
function CreateGroup(String UniqueUserId, String GroupName)
{
local string Url;
local HttpRequestInterface CreateGroupRequest;
local McpGroup FailedGroup;
//Ensure that UniqueUserId and GroupName both have values
if(Len(UniqueUserId) > 0 && Len(GroupName) > 0)
{
// Create HttpRequest
CreateGroupRequest = class'HttpFactory'.static.CreateRequest();
if(CreateGroupRequest != none)
{
// Fill url out using parameters
Url = GetBaseURL() $ CreateGroupUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ UniqueUserId $
"&groupName=" $ GroupName $
"&accessLevel=" $ "OWNER";
// Build our web request with the above URL
CreateGroupRequest.SetURL(URL);
CreateGroupRequest.SetVerb("POST");
CreateGroupRequest.OnProcessRequestComplete = OnCreateGroupRequestComplete;
// Call Web Request
if (!CreateGroupRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
`Log(`Location $ " URL is " $ Url);
}
else
{
OnCreateGroupComplete(FailedGroup, false, "HttpRequest was not be created");
}
}
else
{
OnCreateGroupComplete(FailedGroup, false, "UserId or GroupName wasn't specified");
}
}
/**
* Called once the request/response has completed.
* Used to return any errors and notify any registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnCreateGroupRequestComplete(HttpRequestInterface CreateGroupRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local McpGroup CreatedGroup;
local String JsonString;
local JsonObject ParsedJson;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none && CreateGroupRequest != none)
{
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_CREATED;
Content = HttpResponse.GetContentAsString();
//Set default parameters in case create was not successful
CreatedGroup.OwnerId = CreateGroupRequest.GetURLParameter("uniqueUserId");
CreatedGroup.GroupName = CreateGroupRequest.GetURLParameter("groupName");
JsonString = HttpResponse.GetContentAsString();
if (JsonString != "" && bWasSuccessful)
{
// Parse the json
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
CreatedGroup.GroupId = ParsedJson.GetStringValue("group_id");
CreatedGroup.OwnerId = ParsedJson.GetStringValue("unique_user_id");
CreatedGroup.GroupName = ParsedJson.GetStringValue("group_name");
CreatedGroup.AccessLevel = EMcpGroupAccessLevel(ParsedJson.GetIntValue("access_level") );
}
else
{
`log(`Location@" CreateGroup query did not return a group.");
}
}
OnCreateGroupComplete(CreatedGroup, bWasSuccessful, Content);
}
/**
* Deletes a Group by GroupId
*
* @param UniqueUserId UserId of the owner of the Group
* @param GroupId Id of the group
*/
function DeleteGroup(String UniqueUserId, String GroupId)
{
local string Url;
local HttpRequestInterface DeleteGroupRequest;
//Ensure that UniqueUserId and GroupId both have values
if(Len(UniqueUserId) > 0 && Len(GroupId) > 0)
{
// Delete HttpRequest
DeleteGroupRequest = class'HttpFactory'.static.CreateRequest();
if(DeleteGroupRequest != none)
{
// Fill url out using parameters
Url = GetBaseURL() $ DeleteGroupUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ UniqueUserId $
"&groupId=" $ GroupId;
// Build our web request with the above URL
DeleteGroupRequest.SetVerb("DELETE");
DeleteGroupRequest.SetURL(URL);
DeleteGroupRequest.OnProcessRequestComplete = OnDeleteGroupRequestComplete;
// call WebRequest
if(!DeleteGroupRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
`Log(`Location $ "URL is " $ Url);
}
else
{
OnDeleteGroupComplete(GroupId, false, "HttpRequest could not be completed");
}
}
else
{
OnDeleteGroupComplete(GroupId, false, "UniqueUserId and/or GroupId was not specified");
}
}
/**
* Called once the request/response has completed.
* Used to process the response and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnDeleteGroupRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local String GroupId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none)
{
ResponseCode = HttpResponse.GetResponseCode();
GroupId = HttpResponse.GetURLParameter("GroupId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnDeleteGroupComplete(GroupId, bWasSuccessful, Content);
}
/**
* Queries the backend for the Groups belonging to the supplied UserId
*
* @param UniqueUserId the id of the owner of the groups to return
*/
//Change to be not just owner but all groups userId belongs to
function QueryGroups(String RequesterId)
{
// Cache one group result set per user, instead of only having one array<McpGroup> that gets over
local string Url;
local HttpRequestInterface QueryGroupsRequest;
//Ensure that UniqueUserId and GroupName both have values
if(Len(RequesterId) > 0 )
{
// List HttpRequest
QueryGroupsRequest = class'HttpFactory'.static.CreateRequest();
if (QueryGroupsRequest != none)
{
// Fill it out using parameters
// The server takes one of two parameters
// - uniqueUserId will return only groups owned by the user
// - memberUniqueUserId will return all groups that have the user as a member (including groups owned by that user)
Url = GetBaseURL() $ QueryGroupsUrl $ GetAppAccessURL() $
"&memberUniqueUserId=" $ RequesterId;
// Build our web request with the above URL
QueryGroupsRequest.SetURL(URL);
QueryGroupsRequest.SetVerb("GET");
QueryGroupsRequest.OnProcessRequestComplete = OnQueryGroupsRequestComplete;
// Call Web Request
if(!QueryGroupsRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
else
{
OnQueryGroupsComplete(RequesterId, false, "Http Request could not be created");
}
}
else
{
OnQueryGroupsComplete(RequesterId, false, "RequesterId was not specified");
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
delegate OnQueryGroupsRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Error;
local McpGroup Group;
local string JsonString;
local JsonObject ParsedJson;
local int JsonIndex;
local string RequesterId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
// Both HttpResponse and OriginalRequest need to be present
if (HttpResponse != none && OriginalRequest != none)
{
RequesterId = OriginalRequest.GetURLParameter("memberUniqueUserId");
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
if (bWasSuccessful)
{
JsonString = HttpResponse.GetContentAsString();
if (JsonString != "")
{
// Parse the json
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
// Add each mapping in the json packet if missing
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
Group.OwnerId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("unique_user_id");
Group.GroupId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("group_id");
Group.GroupName = ParsedJson.ObjectArray[JsonIndex].GetStringValue("group_name");
Group.AccessLevel = EMcpGroupAccessLevel(ParsedJson.ObjectArray[JsonIndex].GetIntValue("access_level") );
CacheGroup(RequesterId, Group);
}
}
else
{
Error = "Query did not return any content in it's response.";
`log(`Location $ Error);
}
}
else
{
Error = HttpResponse.GetContentAsString();
}
}
OnQueryGroupsComplete(RequesterId, bWasSuccessful, Error);
}
/**
* Returns the set of groups that related to the specified UserId
* Called after QueryGroups, which fills/updates the GroupLists variable with the response from the server.
* To check for ownership compare the RequesterId to the OwnerId of the group.
*
* @param UserId the request object that was used
* @param GroupList the response object that was generated
*/
function GetGroupList(string UserId, out McpGroupList GroupList)
{
local int GroupListIndex;
// Make sure that userId is valid
if( Len(UserId) > 0 )
{
// Check Cache Variable GroupLists
GroupListIndex = GroupLists.Find('RequesterId', UserId);
if(GroupListIndex != INDEX_NONE)
{
GroupList = GroupLists[GroupListIndex];
}
else
{
`Log(`Location $ " Requester Id not found or GroupLists is empty. Using UserId: " $ UserId);
}
}
else
{
`Log(`Location $ "UserId not specified");
}
}
/**
* Queries the backend for the Groups Members belonging to the specified group
*
* After the Query is returned from the server the data is stored in a local variable GroupLists
* In order to fully fill this variable for a given user you need to run QueryGroups AND QueryGroupMembers.
* Since there are potentially many more GroupMembers than Groups it saves bandwidth to only Query what you need
*
* @param UniqueUserId the id of the owner of the group being queried
* @param GroupId the id of the owner of the groups to return
*/
function QueryGroupMembers(String UniqueUserId, String GroupId)
{
// Cache one group result set per user, instead of only having one array<McpGroup> that gets over
local string Url;
local HttpRequestInterface QueryGroupMembersRequest;
// Make sure that UniqueUserId and GroupId have been given
if( Len(UniqueUserId) > 0 && Len(GroupId) > 0)
{
// List HttpRequest
QueryGroupMembersRequest = class'HttpFactory'.static.CreateRequest();
if (QueryGroupMembersRequest != none)
{
//Create URL parameters
Url = GetBaseURL() $ QueryGroupMembersUrl $ GetAppAccessURL() $
"&groupId=" $ GroupId;
// Build our web request with the above URL
QueryGroupMembersRequest.SetURL(URL);
QueryGroupMembersRequest.SetVerb("GET");
QueryGroupMembersRequest.OnProcessRequestComplete = OnQueryGroupMembersRequestComplete;
// Call WebRequest
if(!QueryGroupMembersRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
else
{
OnQueryGroupMembersComplete(GroupId, false, "HttpRequest not created");
}
}
else
{
OnQueryGroupMembersComplete(GroupId, false, "UserId and/or GroupId not specified");
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
delegate OnQueryGroupMembersRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Error;
local string JsonString;
local JsonObject ParsedJson;
local int JsonIndex;
local EMcpGroupAcceptState AcceptState;
local string MemberId;
local string GroupId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
// Both HttpResponse and OriginalRequest need to be present
if (HttpResponse != none && OriginalRequest != none)
{
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
if (bWasSuccessful)
{
JsonString = HttpResponse.GetContentAsString();
if (JsonString != "")
{
// @todo joeg - Replace with Wes' ImportJson() once it's implemented
// Parse the json
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
// Add each mapping in the json packet if missing
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
MemberId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("unique_user_id");
GroupId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("group_id");
AcceptState = EMcpGroupAcceptState(ParsedJson.ObjectArray[JsonIndex].GetIntValue("status"));
CacheGroupMember(MemberId, GroupId, AcceptState);
}
}
else
{
Error = "Query did not return any content in it's response.";
`log(`Location $ Error);
}
}
else
{
Error = HttpResponse.GetContentAsString();
}
}
OnQueryGroupMembersComplete(GroupId, bWasSuccessful, Error);
}
/**
* Returns the set of Group Members that belong to the specified GroupId
* Called after QueryGroupMembers.
*
* @param GroupId the request object that was used
* @param GroupList the response object that was generated
*/
function GetGroupMembers(String GroupId, out array<McpGroupMember> GroupMembers)
{
local int GroupIndex;
local McpGroupList GroupList;
local McpGroup GroupTemp;
foreach GroupLists(GroupList)
{
foreach GroupList.Groups(GroupTemp, GroupIndex)
{
if(GroupTemp.GroupId == GroupId)
{
GroupMembers = GroupTemp.Members;
}
}
}
}
/**
* Adds an Group Members to the specified Group. Sends this request to MCP to be processed
*
* @param UniqueUserId the user that owns the group
* @param GroupId the group id
* @param MemberIds list of member ids to add to the group
* @param bRequiresAcceptance whether or not members need to accept an invitation to a group
*/
function AddGroupMembers(String UniqueUserId, String GroupId, const out array<String> MemberIds, bool bRequiresAcceptance)
{
local string Url;
local HttpRequestInterface AddGroupMembersRequest;
local string JsonPayload;
local int Index;
// Make sure that UniqueUserId and GroupId have been given
if( Len(UniqueUserId) > 0 && Len(GroupId) > 0)
{
// Create HttpRequest
AddGroupMembersRequest = class'HttpFactory'.static.CreateRequest();
AddGroupMembersRequest.OnProcessRequestComplete = OnAddGroupMembersRequestComplete;
if(AddGroupMembersRequest != none)
{
Url = GetBaseURL() $ AddGroupMembersUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ UniqueUserId $
"&groupId=" $ GroupId $
"&requiresAcceptance=" $ bRequiresAcceptance ? "true" : "false";
// If bRequiresAcceptance is set to false then the group member will be created with a status of Accepted by default
// If it is set to true then the status will be set to Pending and the user will need to accept the invite to officially be part of the group
if(MemberIds.Length > 0)
{
// Make a json string from our list of ids
JsonPayload = "[ ";
for (Index = 0; Index < MemberIds.Length; Index++)
{
JsonPayload $= "\"" $ MemberIds[Index] $ "\"";
// Only add the comma to the string if this isn't the last item
if (Index + 1 < MemberIds.Length)
{
JsonPayload $= ",";
}
}
JsonPayload $= " ]";
// Fill it out using parameters
AddGroupMembersRequest.SetVerb("POST");
AddGroupMembersRequest.SetContentAsString(JsonPayload);
AddGroupMembersRequest.SetURL(URL);
// Call WebRequest
if (!AddGroupMembersRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
`Log(`Location@"URL(" $ Url $ ")");
}
else
{
`Log(`Location@" No MemberIds given.");
}
}
else
{
OnAddGroupMembersComplete(GroupId, false, "HttpRequest was not created");
}
}
else
{
OnAddGroupMembersComplete(GroupId, false, "UserId and/or GroupId not specified");
}
}
/**
* Called once the request/response has completed. Used to process the add mapping result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
delegate OnAddGroupMembersRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local String GroupId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none)
{
GroupId = HttpResponse.GetURLParameter("GroupId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnAddGroupMembersComplete(GroupId, bWasSuccessful, Content);
}
/**
* Remove Group Members from the specified Group. Sends this request to MCP to be processed
*
* @param UniqueUserId the user that owns the group
* @param GroupId the group id
* @param MemberIds list of member ids to add to the group
*/
function RemoveGroupMembers(String UniqueUserId, String GroupId, const out array<String> MemberIds)
{
local string Url;
local HttpRequestInterface RemoveGroupMembersRequest;
local string JsonPayload;
local int Index;
// Make sure that UniqueUserId and GroupId have been given
if( Len(UniqueUserId) > 0 && Len(GroupId) > 0)
{
RemoveGroupMembersRequest = class'HttpFactory'.static.CreateRequest();
if(RemoveGroupMembersRequest != none)
{
Url = GetBaseURL() $ RemoveGroupMembersUrl $ GetAppAccessURL() $
"&groupId=" $ GroupId;
if(MemberIds.Length > 0)
{
// Make a json string from our list of ids
JsonPayload = "[ ";
for (Index = 0; Index < MemberIds.Length; Index++)
{
JsonPayload $= "\"" $ MemberIds[Index] $ "\"";
// Only add the comma to the string if this isn't the last item
if (Index + 1 < MemberIds.Length)
{
JsonPayload $= ",";
}
}
JsonPayload $= " ]";
RemoveGroupMembersRequest.SetURL(URL);
RemoveGroupMembersRequest.SetContentAsString(JsonPayload);
RemoveGroupMembersRequest.SetVerb("DELETE");
RemoveGroupMembersRequest.OnProcessRequestComplete = OnRemoveGroupMembersRequestComplete;
// Call the request
if (!RemoveGroupMembersRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
else
{
`Log(`Location@" No MemberIds given.");
}
}
else
{
OnRemoveGroupMembersComplete(GroupId, false, "Http request was not created");
}
}
else
{
OnRemoveGroupMembersComplete(GroupId, false, "UniqueUserId and/or GroupId was not specified");
}
}
/**
* Called once the request/response has completed. Used to process the add mapping result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnRemoveGroupMembersRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local String GroupId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none)
{
GroupId = HttpResponse.GetURLParameter("GroupId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnRemoveGroupMembersComplete(GroupId, bWasSuccessful, Content);
}
/**
* Deletes all Groups that belong to UniqueUserId
*
* @param UniqueUserId UserId of the owner of the Group
*/
function DeleteAllGroups(String UniqueUserId)
{
local string Url;
local HttpRequestInterface DeleteGroupRequest;
// Make sure that UniqueUserId and GroupId have been given
if( Len(UniqueUserId) > 0 )
{
// Delete HttpRequest
DeleteGroupRequest = class'HttpFactory'.static.CreateRequest();
if(DeleteGroupRequest != none)
{
// Fill it out using parameters
DeleteGroupRequest.SetVerb("DELETE");
Url = GetBaseURL() $ DeleteAllGroupsUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ UniqueUserId;
DeleteGroupRequest.SetURL(URL);
`log("URL="@DeleteGroupRequest.GetURL());
DeleteGroupRequest.OnProcessRequestComplete = OnDeleteGroupRequestComplete;
// call WebRequest
if(!DeleteGroupRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
else
{
OnDeleteAllGroupsComplete(UniqueUserId, false, "HttpRequest was not created");
}
}
else
{
OnDeleteAllGroupsComplete(UniqueUserId, false, "UniqueUserId was not specified");
}
}
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
function OnDeleteAllGroupsRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local string RequesterId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
// Both HttpResponse and OriginalRequest need to be present
if (HttpResponse != none && OriginalRequest != none)
{
RequesterId = OriginalRequest.GetURLParameter("uniqueUserId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnDeleteAllGroupsComplete(RequesterId, bWasSuccessful, Content);
}
/**
* Set's the a Member's membership status to Accept or Reject
* based on the value of bShouldAccept
*
* @param UniqueUserId User who's status is to be update
* @param GroupId
* @param bShouldAccept 1 = accepted 0 = rejected
*/
function AcceptGroupInvite(String UniqueUserId, String GroupId, bool bShouldAccept)
{
local string Url;
local HttpRequestInterface AcceptGroupInviteRequest;
// Make sure that UniqueUserId and GroupId have been given
if( Len(UniqueUserId) > 0 && Len(GroupId) > 0)
{
// Create HttpRequest
AcceptGroupInviteRequest = class'HttpFactory'.static.CreateRequest();
if(AcceptGroupInviteRequest != none)
{
Url = GetBaseURL() $ AcceptGroupInviteUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ UniqueUserId $
"&groupId=" $ GroupId $
"&status=" $ bShouldAccept ? "accepted" : "rejected";
// Fill it out using parameters
AcceptGroupInviteRequest.SetVerb("POST");
AcceptGroupInviteRequest.SetURL(URL);
AcceptGroupInviteRequest.OnProcessRequestComplete = OnAcceptGroupInviteRequestComplete;
// Call WebRequest
`log("Calling Process Request");
if(!AcceptGroupInviteRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
else
{
OnAcceptGroupInviteComplete(GroupId, false, "HttpRequest not created");
}
}
else
{
OnAcceptGroupInviteComplete(GroupId, false, "UniqueUserId or GroupId was not specified");
}
}
/**
* Called once the request/response has completed. Used to process the add mapping result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
delegate OnAcceptGroupInviteRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local String GroupId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none)
{
GroupId = HttpResponse.GetURLParameter("GroupId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnAcceptGroupInviteComplete(GroupId, bWasSuccessful, Content);
}
/**
*Store group in an object in memory instead of having to query the server again for it
*
* @param Group to be placed in the cache
*/
function CacheGroup(string RequesterId, McpGroup Group)
{
local int AddAt;
local int GroupIndex;
local int GroupListIndex;
local McpGroupList UserGroupList;
local bool bWasFound;
//Find the user's cached group list in collection of users group lists, GroupLists
//TODO Is there a better way to do this? Have to rely on the response not being empty, though if it is then we don't do anything so that's fine.
// - More importantly we're querying the GroupLists for every entry returned which isn't as efficient.
// - Could just set GroupListIndex and then check if it's been set ...
bWasFound = false;
GroupListIndex = GroupLists.Find('RequesterId', RequesterId);
if(GroupListIndex != INDEX_NONE)
{
UserGroupList = GroupLists[GroupListIndex];
// Search the array for any existing adding only when missing
for (GroupIndex = 0; GroupIndex < UserGroupList.Groups.Length && !bWasFound; GroupIndex++)
{
bWasFound = Group.GroupId == UserGroupList.Groups[GroupIndex].GroupId;
}
// Add this one since it wasn't found
if (!bWasFound)
{
AddAt = UserGroupList.Groups.Length;
UserGroupList.Groups.Length = AddAt + 1;
UserGroupList.Groups[AddAt] = Group;
GroupLists[GroupListIndex] = UserGroupList;
}
`log(`Location $ " GroupName: " $ UserGroupList.Groups[AddAt].GroupName);
}
else
{
// Add User with this first returned group since it wasn't found
AddAt = GroupLists.Length;
GroupLists.Length = AddAt +1;
GroupLists[AddAt].RequesterId = Group.OwnerId;
GroupLists[AddAt].Groups[0]=Group;
}
}
/**
*Store group member in an object in memory instead of having to query the server again for it
*
* @param MemberId to be placed in the cache
* @param GroupId to be placed in the cache
* @param intAcceptState whether the group member's status is accepted, pending or rejected
*/
function CacheGroupMember(String MemberId, String GroupId, EMcpGroupAcceptState AcceptState)
{
local int MemberIndex;
local McpGroupList GroupList;
local int GroupListIndex;
local McpGroup GroupTemp;
local int GroupIndex;
local int AddAt;
// Have the variables been passed in properly
if(Len(MemberId) > 0 && Len(GroupId) > 0 && Len(AcceptState) > 0)
{
// Look at each GroupList (userId:<groups>) mapping to see where to add/update this member
foreach GroupLists(GroupList, GroupListIndex)
{
// For each group related to the user see if it's the GroupId specified
foreach GroupList.Groups(GroupTemp, GroupIndex)
{
// If the group is found Update the Member field
// This will potential run multiple times as it will be stored under both owners and members in GroupLists
if(GroupTemp.GroupId == GroupId)
{
// Locate the proper place to update or add the member
MemberIndex = GroupTemp.Members.find('MemberId', MemberId);
if(MemberIndex == INDEX_NONE)
{
// No MemberId found so add the member
AddAt = GroupTemp.Members.Length;
GroupTemp.Members.Length = AddAt +1;
GroupTemp.Members[AddAt].MemberId = MemberId;
GroupTemp.Members[AddAt].AcceptState = AcceptState;
}
else
{
// GroupId and MemberId have been confirmed so just update the accept state if it's changed
if(GroupTemp.Members[MemberIndex].AcceptState != AcceptState)
{
GroupTemp.Members[MemberIndex].AcceptState = AcceptState;
}
}
// Set the group at the location to the updated (or unchanged) group
GroupList.Groups[GroupIndex] = GroupTemp;
}
}
// Set the GroupList at the location to the updated (or unchanged) GroupList
GroupLists[GroupListIndex] = GroupList;
}
}
else
{
`Log(`Location@" Either the MemberId, GroupId, or AcceptState was not Specified.");
}
}

View File

@ -0,0 +1,89 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for mapping account ids and the factory method
* for creating the registered implementing object
*/
class McpIdMappingBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config String McpIdMappingClassName;
/**
* Maps an McpId to an external account (Facebook, G+, Twitter, etc.)
*/
struct McpIdMapping
{
/** The McpId that owns this mapping */
var String McpId;
/** The external account id that is being mapped to the McpId */
var String ExternalId;
/** The type of account (Facebook, G+, Twitter, GameCenter, etc.) this is to avoid name collisions */
var String ExternalType;
};
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpIdMappingBase CreateInstance()
{
local class<McpIdMappingBase> McpIdMappingBaseClass;
local McpIdMappingBase NewInstance;
McpIdMappingBaseClass = class<McpIdMappingBase>(DynamicLoadObject(default.McpIdMappingClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpIdMappingBaseClass != None)
{
NewInstance = new McpIdMappingBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Adds an external account mapping. Sends this request to MCP to be processed
*
* @param McpId the account to add the mapping to
* @param ExternalId the external account that is being mapped to this account
* @param ExternalType the type of account for disambiguation
*/
function AddMapping(String McpId, String ExternalId, String ExternalType);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the MCP account that this external account is being added to
* @param ExternalId the account id that was being mapped
* @param ExternalType the external account type that was being mapped
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnAddMappingComplete(String McpId, String ExternalId, String ExternalType, bool bWasSuccessful, String Error);
/**
* Queries the backend for the McpIds of the list of external ids of a specific type
*
* @param ExternalIds the set of ids to get McpIds for
* @param ExternalType the type of account that is being mapped to McpIds
*/
function QueryMappings(const out array<String> ExternalIds, String ExternalType);
/**
* Called once the query results come back from the server to indicate success/failure of the request
*
* @param ExternalType the external account type that was being queried for
* @param bWasSuccessful whether the query succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryMappingsComplete(String ExternalType, bool bWasSuccessful, String Error);
/**
* Returns the set of id mappings that match the requested account type
*
* @param ExternalType the account type that we want the mappings for
* @param IdMappins the out array that gets the copied data
*/
function GetIdMappings(String ExternalType, out array<McpIdMapping> IdMappings);

View File

@ -0,0 +1,301 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Concrete implementation for mapping McpIds to external account ids
*/
class McpIdMappingManager extends McpIdMappingBase;
/**
* Holds the set of mapped accounts that we've downloaded
*/
var array<McpIdMapping> AccountMappings;
/** The URL to use when making add mapping requests */
var config String AddMappingUrl;
/** The URL to use when making query mapping requests */
var config String QueryMappingUrl;
/** Holds the state information for an outstanding add mapping request */
struct AddMappingRequest
{
/** The McpId the add is happening to */
var string McpId;
/** The external account id that is being mapped to a MCP id */
var string ExternalId;
/** The account type that is being mapped */
var string ExternalType;
/** The request object for this request */
var HttpRequestInterface Request;
};
/** The set of add mapping requests that are pending */
var array<AddMappingRequest> AddMappingRequests;
/** Holds the state information for an outstanding query mapping request */
struct QueryMappingRequest
{
/** The account type that is being mapped */
var string ExternalType;
/** The request object for this request */
var HttpRequestInterface Request;
};
/** The set of query mapping requests that are pending */
var array<QueryMappingRequest> QueryMappingRequests;
/**
* Adds an external account mapping. Sends this request to MCP to be processed
*
* @param McpId the account to add the mapping to
* @param ExternalId the external account that is being mapped to this account
* @param ExternalType the type of account for disambiguation
*/
function AddMapping(String McpId, String ExternalId, String ExternalType)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ AddMappingUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&externalAccountId=" $ ExternalId $
"&externalAccountType=" $ ExternalType;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnAddMappingRequestComplete;
// Store off the data for reporting later
AddAt = AddMappingRequests.Length;
AddMappingRequests.Length = AddAt + 1;
AddMappingRequests[AddAt].McpId = McpId;
AddMappingRequests[AddAt].ExternalId = ExternalId;
AddMappingRequests[AddAt].ExternalType = ExternalType;
AddMappingRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start AddMapping web request for URL(" $ Url $ ")");
}
`Log("URL is " $ Url);
}
}
/**
* Called once the request/response has completed. Used to process the add mapping result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnAddMappingRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int AddAt;
local int ResponseCode;
// Search for the corresponding entry in the array
Index = AddMappingRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
`Log("Account mapping McpId(" $ AddMappingRequests[Index].McpId $ "), ExternalId(" $
AddMappingRequests[Index].ExternalId $ "), ExternalType(" $
AddMappingRequests[Index].ExternalType $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
// Add this item to our list
if (bWasSuccessful)
{
AddAt = AccountMappings.Length;
AccountMappings.Length = AddAt + 1;
AccountMappings[AddAt].McpId = AddMappingRequests[Index].McpId;
AccountMappings[AddAt].ExternalId = AddMappingRequests[Index].ExternalId;
AccountMappings[AddAt].ExternalType = AddMappingRequests[Index].ExternalType;
}
// Notify anyone waiting on this
OnAddMappingComplete(AddMappingRequests[Index].McpId,
AddMappingRequests[Index].ExternalId,
AddMappingRequests[Index].ExternalType,
bWasSuccessful,
Response.GetContentAsString());
// Done with the pending request
AddMappingRequests.Remove(Index,1);
}
}
/**
* Queries the backend for the McpIds of the list of external ids of a specific type
*
* @param ExternalIds the set of ids to get McpIds for
* @param ExternalType the type of account that is being mapped to McpIds
*/
function QueryMappings(const out array<String> ExternalIds, String ExternalType)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
local string JsonPayload;
local int Index;
local bool bFirst;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ QueryMappingUrl $ GetAppAccessURL() $
"&externalAccountType=" $ ExternalType;
// Make a json string from our list of ids
JsonPayload = "[ ";
bFirst = true;
for (Index = 0; Index < ExternalIds.Length; Index++)
{
if (Len(ExternalIds[Index]) > 0)
{
if (!bFirst)
JsonPayload $= ",";
bFirst = false;
JsonPayload $= "\"" $ ExternalIds[Index] $ "\"";
}
}
JsonPayload $= " ]";
`log("QueryMappings.JsonPayload:\n" $ JsonPayload);
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetContentAsString(JsonPayload);
Request.SetVerb("POST");
Request.SetHeader("Content-Type","multipart/form-data");
Request.OnProcessRequestComplete = OnQueryMappingsRequestComplete;
// Store off the data for reporting later
AddAt = QueryMappingRequests.Length;
QueryMappingRequests.Length = AddAt + 1;
QueryMappingRequests[AddAt].ExternalType = ExternalType;
QueryMappingRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start QueryMappings web request for URL(" $ Url $ ")");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnQueryMappingsRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int AddAt;
local int ResponseCode;
local string JsonString;
local JsonObject ParsedJson;
local int JsonIndex;
local int AccountIndex;
local bool bWasFound;
local string McpId;
local string ExternalId;
local string ExternalType;
// Search for the corresponding entry in the array
Index = QueryMappingRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
`Log("Account mapping query for ExternalType(" $
QueryMappingRequests[Index].ExternalType $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
// Add this item to our list
if (bWasSuccessful)
{
JsonString = Response.GetContentAsString();
if (JsonString != "")
{
`Log("JSON for account query = \r\n" $ JsonString);
// @todo joeg - Replace with Wes' ImportJson() once it's implemented
// Parse the json
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
// Add each mapping in the json packet if missing
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
McpId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("unique_user_id");
ExternalId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("external_account_id");
ExternalType = ParsedJson.ObjectArray[JsonIndex].GetStringValue("external_account_type");
bWasFound = false;
// Search the array for any existing adding only when missing
for (AccountIndex = 0; AccountIndex < AccountMappings.Length && !bWasFound; AccountIndex++)
{
bWasFound = McpId == AccountMappings[AccountIndex].McpId &&
ExternalId == AccountMappings[AccountIndex].ExternalId &&
ExternalType == AccountMappings[AccountIndex].ExternalType;
}
// Add this one since it wasn't found
if (!bWasFound)
{
AddAt = AccountMappings.Length;
AccountMappings.Length = AddAt + 1;
AccountMappings[AddAt].McpId = McpId;
AccountMappings[AddAt].ExternalId = ExternalId;
AccountMappings[AddAt].ExternalType = ExternalType;
}
}
}
}
// Notify anyone waiting on this
OnQueryMappingsComplete(QueryMappingRequests[Index].ExternalType,
bWasSuccessful,
Response.GetContentAsString());
// Done with the pending request
QueryMappingRequests.Remove(Index,1);
}
}
/**
* Returns the set of id mappings that match the requested account type
*
* @param ExternalType the account type that we want the mappings for
* @param IdMappings the out array that gets the copied data
*/
function GetIdMappings(String ExternalType, out array<McpIdMapping> IdMappings)
{
local int Index;
local int AddAt;
IdMappings.Length = 0;
// Search through for all mappings that match the desired type
for (Index = 0; Index < AccountMappings.Length; Index++)
{
if (AccountMappings[Index].ExternalType == ExternalType)
{
AddAt = IdMappings.Length;
IdMappings.Length = AddAt + 1;
IdMappings[AddAt] = AccountMappings[Index];
}
}
}

View File

@ -0,0 +1,524 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the concrete implementation
*/
class McpManagedValueManager extends McpManagedValueManagerBase;
/** The URL to use when making save slot */
var config String CreateSaveSlotUrl;
/** The URL to use when reading save slot */
var config String ReadSaveSlotUrl;
/** The URL to use when updating a value */
var config String UpdateValueUrl;
/** The URL to use when deleting a value */
var config String DeleteValueUrl;
/** The list of all save slots that are being managed */
var array<ManagedValueSaveSlot> SaveSlots;
/** Holds the state information for an outstanding save slot request */
struct SaveSlotRequestState
{
/** The MCP id that was returned by the backend */
var string McpId;
/** The save slot involved */
var string SaveSlot;
/** The request object for this request */
var HttpRequestInterface Request;
};
/** Holds the state information used by requests that act on a value id */
struct ValueRequestState extends SaveSlotRequestState
{
var Name ValueId;
};
/** The set of create save slot requests that are pending */
var array<SaveSlotRequestState> CreateSaveSlotRequests;
/** The set of read save slot requests that are pending */
var array<SaveSlotRequestState> ReadSaveSlotRequests;
/** The set of update value requests that are pending */
var array<ValueRequestState> UpdateValueRequests;
/** The set of update value requests that are pending */
var array<ValueRequestState> DeleteValueRequests;
/**
* Creates the user's specified save slot
*
* @param McpId the id of the user that requested the create
* @param SaveSlot the save slot that is being create
*/
function CreateSaveSlot(String McpId, String SaveSlot)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ CreateSaveSlotUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&saveSlotId=" $ SaveSlot;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnCreateSaveSlotRequestComplete;
// Store off the data for reporting later
AddAt = CreateSaveSlotRequests.Length;
CreateSaveSlotRequests.Length = AddAt + 1;
CreateSaveSlotRequests[AddAt].McpId = McpId;
CreateSaveSlotRequests[AddAt].SaveSlot = SaveSlot;
CreateSaveSlotRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start CreateSaveSlot web request for URL(" $ Url $ ")");
}
`Log("Create save slot URL is " $ Url);
}
}
/**
* Called once the request/response has completed. Used to process the create save slot result
* and notify any registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnCreateSaveSlotRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local string ResponseString;
// Search for the corresponding entry in the array
Index = CreateSaveSlotRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
ResponseString = Response.GetContentAsString();
// Parse the JSON payload of default values for this save slot
ParseValuesForSaveSlot(CreateSaveSlotRequests[Index].McpId,
CreateSaveSlotRequests[Index].SaveSlot,
ResponseString);
}
// Notify anyone waiting on this
OnCreateSaveSlotComplete(CreateSaveSlotRequests[Index].McpId,
CreateSaveSlotRequests[Index].SaveSlot,
bWasSuccessful,
Response.GetContentAsString());
`Log("Create save slot McpId(" $ CreateSaveSlotRequests[Index].McpId $ "), SaveSlot(" $
CreateSaveSlotRequests[Index].SaveSlot $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
CreateSaveSlotRequests.Remove(Index,1);
}
}
/**
* Searches the stored save slots for the ones for this user
*
* @param McpId the user being searched for
* @param SaveSlot the save slot being searched for
*
* @return the index of the save slot if found
*/
function int FindSaveSlotIndex(String McpId, String SaveSlot)
{
local int SaveSlotIndex;
// Search all of the save slots for one that matches the user and slot id
for (SaveSlotIndex = 0; SaveSlotIndex < SaveSlots.Length; SaveSlotIndex++)
{
if (SaveSlots[SaveSlotIndex].OwningMcpId == McpId &&
SaveSlots[SaveSlotIndex].SaveSlot == SaveSlot)
{
return SaveSlotIndex;
}
}
return INDEX_NONE;
}
/**
* Parses the json of managed values for a user's save slot
*
* @param McpId the user that owns the data
* @param SaveSlot the save slot where the data is stored
* @param JsonPayload the json data to parse
*/
function ParseValuesForSaveSlot(String McpId, String SaveSlot, String JsonPayload)
{
local JsonObject ParsedJson;
local int JsonIndex;
local int SaveSlotIndex;
local int ManagedValueIndex;
local name ValueId;
local int Value;
// Find the save slot for the user
SaveSlotIndex = FindSaveSlotIndex(McpId, SaveSlot);
if (SaveSlotIndex == INDEX_NONE)
{
// Add the save slot since it is missing
SaveSlotIndex = SaveSlots.Length;
SaveSlots.Length = SaveSlotIndex + 1;
SaveSlots[SaveSlotIndex].OwningMcpId = McpId;
SaveSlots[SaveSlotIndex].SaveSlot = SaveSlot;
}
// @todo joeg - Replace with Wes' ImportJson() once it's implemented with proper variable naming support
ParsedJson = class'JsonObject'.static.DecodeJson(JsonPayload);
// Add/update each managed value to the user's save slot
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
// Grab the value and id we need to store
ValueId = name(ParsedJson.ObjectArray[JsonIndex].GetStringValue("value_id"));
Value = ParsedJson.ObjectArray[JsonIndex].GetIntValue("value");
// Find the existing managed value
ManagedValueIndex = SaveSlots[SaveSlotIndex].Values.Find('ValueId', ValueId);
if (ManagedValueIndex == INDEX_NONE)
{
// Not stored yet, so add one
ManagedValueIndex = SaveSlots[SaveSlotIndex].Values.Length;
SaveSlots[SaveSlotIndex].Values.Length = ManagedValueIndex + 1;
SaveSlots[SaveSlotIndex].Values[ManagedValueIndex].ValueId = ValueId;
}
// Store the updated value
SaveSlots[SaveSlotIndex].Values[ManagedValueIndex].Value = Value;
}
}
/**
* Reads all of the values in the user's specified save slot
*
* @param McpId the id of the user that requested the read
* @param SaveSlot the save slot that is being read
*/
function ReadSaveSlot(String McpId, String SaveSlot)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ ReadSaveSlotUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&saveSlotId=" $ SaveSlot;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("GET");
Request.OnProcessRequestComplete = OnReadSaveSlotRequestComplete;
// Store off the data for reporting later
AddAt = ReadSaveSlotRequests.Length;
ReadSaveSlotRequests.Length = AddAt + 1;
ReadSaveSlotRequests[AddAt].McpId = McpId;
ReadSaveSlotRequests[AddAt].SaveSlot = SaveSlot;
ReadSaveSlotRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start ReadSaveSlot web request for URL(" $ Url $ ")");
}
`Log("Read save slot URL is " $ Url);
}
}
/**
* Called once the request/response has completed. Used to process the read save slot result
* and notify any registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnReadSaveSlotRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local string ResponseString;
// Search for the corresponding entry in the array
Index = ReadSaveSlotRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
ResponseString = Response.GetContentAsString();
// Parse the JSON payload of default values for this save slot
ParseValuesForSaveSlot(ReadSaveSlotRequests[Index].McpId,
ReadSaveSlotRequests[Index].SaveSlot,
ResponseString);
}
// Notify anyone waiting on this
OnReadSaveSlotComplete(ReadSaveSlotRequests[Index].McpId,
ReadSaveSlotRequests[Index].SaveSlot,
bWasSuccessful,
Response.GetContentAsString());
`Log("Read save slot McpId(" $ ReadSaveSlotRequests[Index].McpId $ "), SaveSlot(" $
ReadSaveSlotRequests[Index].SaveSlot $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
ReadSaveSlotRequests.Remove(Index,1);
}
}
/**
* @return The list of values for the requested user's specified save slot
*/
function array<ManagedValue> GetValues(String McpId, String SaveSlot)
{
local int SaveSlotIndex;
local array<ManagedValue> EmptyArray;
// Find the slot and then return the array of values stored there
SaveSlotIndex = FindSaveSlotIndex(McpId, SaveSlot);
if (SaveSlotIndex != INDEX_NONE)
{
return SaveSlots[SaveSlotIndex].Values;
}
// To avoid the script warning
EmptyArray.Length = 0;
return EmptyArray;
}
/**
* @return The value the server returned for the requested value id from the user's specific save slot
*/
function int GetValue(String McpId, String SaveSlot, Name ValueId)
{
local int SaveSlotIndex;
local int ValueIndex;
local int Value;
// Find the slot first
SaveSlotIndex = FindSaveSlotIndex(McpId, SaveSlot);
if (SaveSlotIndex != INDEX_NONE)
{
// Find the requested value
ValueIndex = SaveSlots[SaveSlotIndex].Values.Find('ValueId', ValueId);
if (ValueIndex != INDEX_NONE)
{
Value = SaveSlots[SaveSlotIndex].Values[ValueIndex].Value;
}
}
return Value;
}
/**
* Updates a specific value in the user's specified save slot
*
* @param McpId the id of the user that requested the update
* @param SaveSlot the save slot that was being updated
* @param ValueId the value that the server should update
* @param Value the value to apply as the update (delta or absolute determined by the server)
*/
function UpdateValue(String McpId, String SaveSlot, Name ValueId, int Value)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ UpdateValueUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&saveSlotId=" $ SaveSlot $
"&valueId=" $ ValueId $
"&value=" $ Value;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnUpdateValueRequestComplete;
// Store off the data for reporting later
AddAt = UpdateValueRequests.Length;
UpdateValueRequests.Length = AddAt + 1;
UpdateValueRequests[AddAt].McpId = McpId;
UpdateValueRequests[AddAt].SaveSlot = SaveSlot;
UpdateValueRequests[AddAt].ValueId = ValueId;
UpdateValueRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start UpdateValue web request for URL(" $ Url $ ")");
}
`Log("Update value URL is " $ Url);
}
}
/**
* Called once the request/response has completed. Used to process the update value result
* and notify any registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnUpdateValueRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local string ResponseString;
local int UpdatedValue;
// Search for the corresponding entry in the array
Index = UpdateValueRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
ResponseString = "[" $ Response.GetContentAsString() $ "]";
// Parse the JSON payload of default values for this save slot
ParseValuesForSaveSlot(UpdateValueRequests[Index].McpId,
UpdateValueRequests[Index].SaveSlot,
ResponseString);
}
UpdatedValue = GetValue(UpdateValueRequests[Index].McpId, UpdateValueRequests[Index].SaveSlot, UpdateValueRequests[Index].ValueId);
// Notify anyone waiting on this
OnUpdateValueComplete(UpdateValueRequests[Index].McpId,
UpdateValueRequests[Index].SaveSlot,
UpdateValueRequests[Index].ValueId,
UpdatedValue,
bWasSuccessful,
Response.GetContentAsString());
`Log("Update value McpId(" $ UpdateValueRequests[Index].McpId $ "), SaveSlot(" $
UpdateValueRequests[Index].SaveSlot $ "), ValueId(" $
UpdateValueRequests[Index].ValueId $ "), Value(" $
UpdatedValue $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
UpdateValueRequests.Remove(Index,1);
}
}
/**
* Deletes a value from the user's specified save slot
*
* @param McpId the id of the user that requested the delete
* @param SaveSlot the save slot that is having the value deleted from
* @param ValueId the value id for the server to delete
*/
function DeleteValue(String McpId, String SaveSlot, Name ValueId)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ DeleteValueUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&saveSlotId=" $ SaveSlot $
"&valueId=" $ ValueId;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("DELETE");
Request.OnProcessRequestComplete = OnDeleteValueRequestComplete;
// Store off the data for reporting later
AddAt = DeleteValueRequests.Length;
DeleteValueRequests.Length = AddAt + 1;
DeleteValueRequests[AddAt].McpId = McpId;
DeleteValueRequests[AddAt].SaveSlot = SaveSlot;
DeleteValueRequests[AddAt].ValueId = ValueId;
DeleteValueRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start DeleteValue web request for URL(" $ Url $ ")");
}
`Log("Delete value URL is " $ Url);
}
}
/**
* Called once the request/response has completed. Used to process the delete value result
* and notify any registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnDeleteValueRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local int SaveSlotIndex;
local int ValueIndex;
// Search for the corresponding entry in the array
Index = DeleteValueRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Find the slot first
SaveSlotIndex = FindSaveSlotIndex(DeleteValueRequests[Index].McpId, DeleteValueRequests[Index].SaveSlot);
if (SaveSlotIndex != INDEX_NONE)
{
// Find the requested value
ValueIndex = SaveSlots[SaveSlotIndex].Values.Find('ValueId', DeleteValueRequests[Index].ValueId);
if (ValueIndex != INDEX_NONE)
{
// Remove it from the array
SaveSlots[SaveSlotIndex].Values.Remove(ValueIndex, 1);
}
}
}
// Notify anyone waiting on this
OnDeleteValueComplete(DeleteValueRequests[Index].McpId,
DeleteValueRequests[Index].SaveSlot,
DeleteValueRequests[Index].ValueId,
bWasSuccessful,
Response.GetContentAsString());
`Log("Delete value McpId(" $ DeleteValueRequests[Index].McpId $ "), SaveSlot(" $
DeleteValueRequests[Index].SaveSlot $ "), ValueId(" $
DeleteValueRequests[Index].ValueId $ ") was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
DeleteValueRequests.Remove(Index,1);
}
}

View File

@ -0,0 +1,142 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base interface for manipulating a user's managed values
*/
class McpManagedValueManagerBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config String McpManagedValueManagerClassName;
/**
* Name to value mapping for a value managed on the MCP server
*/
struct ManagedValue
{
/** The ID of the value */
var Name ValueId;
/** The value the server from the server */
var int Value;
};
/**
* Holds a single user's save slot information for managed values
*/
struct ManagedValueSaveSlot
{
/** The owner of this save slot */
var String OwningMcpId;
/** The save slot id */
var String SaveSlot;
/** The list of managed values in this save slot */
var array<ManagedValue> Values;
};
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpManagedValueManagerBase CreateInstance()
{
local class<McpManagedValueManagerBase> McpManagedValueManagerBaseClass;
local McpManagedValueManagerBase NewInstance;
McpManagedValueManagerBaseClass = class<McpManagedValueManagerBase>(DynamicLoadObject(default.McpManagedValueManagerClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpManagedValueManagerBaseClass != None)
{
NewInstance = new McpManagedValueManagerBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Creates the user's specified save slot
*
* @param McpId the id of the user that requested the create
* @param SaveSlot the save slot that is being create
*/
function CreateSaveSlot(String McpId, String SaveSlot);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that requested the save slot create
* @param SaveSlot the save slot that was created
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnCreateSaveSlotComplete(string McpId, string SaveSlot, bool bWasSuccessful, String Error);
/**
* Reads all of the values in the user's specified save slot
*
* @param McpId the id of the user that requested the read
* @param SaveSlot the save slot that is being read
*/
function ReadSaveSlot(String McpId, String SaveSlot);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that requested the read
* @param SaveSlot the save slot that was being read
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnReadSaveSlotComplete(string McpId, string SaveSlot, bool bWasSuccessful, String Error);
/**
* @return The list of values for the requested user's specified save slot
*/
function array<ManagedValue> GetValues(String McpId, String SaveSlot);
/**
* @return The value the server returned for the requested value id from the user's specific save slot
*/
function int GetValue(String McpId, String SaveSlot, Name ValueId);
/**
* Updates a specific value in the user's specified save slot
*
* @param McpId the id of the user that requested the update
* @param SaveSlot the save slot that was being updated
* @param ValueId the value that the server should update
* @param Value the value to apply as the update (delta or absolute determined by the server)
*/
function UpdateValue(String McpId, String SaveSlot, Name ValueId, int Value);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that requested the update
* @param SaveSlot the save slot that was being updated
* @param ValueId the value id that was updated
* @param Value the value that the server returned as part of the update (in case the server overrides it)
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnUpdateValueComplete(string McpId, string SaveSlot, Name ValueId, int Value, bool bWasSuccessful, String Error);
/**
* Deletes a value from the user's specified save slot
*
* @param McpId the id of the user that requested the delete
* @param SaveSlot the save slot that is having the value deleted from
* @param ValueId the value id for the server to delete
*/
function DeleteValue(String McpId, String SaveSlot, Name ValueId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that requested the delete
* @param SaveSlot the save slot that was having the value deleted from
* @param ValueId the value id that the server deleted
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteValueComplete(string McpId, string SaveSlot, Name ValueId, bool bWasSuccessful, String Error);

View File

@ -0,0 +1,223 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for McpMessages and the factory method
* for creating the registered implementing object
*/
class McpMessageBase extends McpServiceBase
native
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config string McpMessageManagerClassName;
/** Compression types supported */
enum EMcpMessageCompressionType
{
MMCT_NONE,
MMCT_LZO,
MMCT_ZLIB
};
/** Default Compression Type */
var config EMcpMessageCompressionType CompressionType;
/**
* Message
*/
struct native McpMessage
{
/**
* Unique id to use as a key for this message
*/
var String MessageId;
/**
* The user id to deliver this message to
*/
var String ToUniqueUserId;
/**
* The user id this message is from
*/
var String FromUniqueUserId;
/**
* The friendly name of the user sending the data
*/
var String FromFriendlyName;
/**
* The application specific message type
*/
var String MessageType;
/**
* The date until this message is no longer valid (should be deleted)
*/
var String ValidUntil;
/**
* The compression type of this message so the client knows how to de-compress the payload
*/
var EMcpMessageCompressionType MessageCompressionType;
};
/**
* List of Messages belonging to one user
*/
struct native McpMessageList
{
/** User that User the messages were Sent To */
var string ToUniqueUserId;
/** Collection of groups that the user owns OR belongs to */
var array<McpMessage> Messages;
};
/**
* Message Contents
*/
struct native McpMessageContents
{
/**
* Message Id
*/
var string MessageId;
/**
* Payload holding the contents of the message
*/
var array<byte> MessageContents;
};
/** Holds the MessageContents for each user in memory */
var array<McpMessageContents> MessageContentsList;
/** Holds the members for each user in memory */
var array<McpMessageList> MessageLists;
/**
* Create Instance of an McpMessage
* @return the object that implements this interface or none if missing or failed to create/load
*/
static final function McpMessageBase CreateInstance()
{
local class<McpMessageBase> McpMessageBaseClass;
local McpMessageBase NewInstance;
McpMessageBaseClass = class<McpMessageBase>(DynamicLoadObject(default.McpMessageManagerClassName,class'Class'));
if (McpMessageBaseClass != None)
{
NewInstance = new McpMessageBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Creates the URL and sends the request to create a Message.
* - This updates the message on the server. QueryMessages will need to be
* - run again before GetMessages will reflect the this new message.
* @param ToUniqueUserIds the ids of users to send a message to
* @param FromUniqueUserId of the user who sent the message
* @param FromFriendlyName The friendly name of the user sending the data
* @param MessageType The application specific message type
* @param PushMessage to be sent to user via push notifications
* @param ValidUntil The date until this message is no longer valid (should be deleted)
* @param MessageContents payload of the message
*/
function CreateMessage(
const out array<String> ToUniqueUserIds,
String FromUniqueUserId,
String FromFriendlyName,
String MessageType,
String PushMessage,
String ValidUntil,
const out array<byte> MessageContents);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param Message the group id of the group that was created
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnCreateMessageComplete(McpMessage Message, bool bWasSuccessful, String Error);
/**
* Deletes a Message by MessageId
*
* @param MessageId Id of the group
*/
function DeleteMessage(String MessageId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param MessageId the group id of the group that was Deleted
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteMessageComplete(String MessageId, bool bWasSuccessful, String Error);
/**
* Queries the backend for the Messages belonging to the supplied UserId
*
* @param UniqueUserId the id of the owner of the groups to return
*/
function QueryMessages(String ToUniqueUserId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param UserId the user id of the groups that were queried
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryMessagesComplete(string UserId, bool bWasSuccessful, String Error);
/**
* Returns the set of messages that sent to the specified UserId
* Called after QueryMessages.
*
* @param ToUniqueUserId the user the messages were sent to
* @param MessageList collection of messages
*/
function GetMessageList(string ToUniqueUserId, out McpMessageList MessageList);
/**
* Queries the back-end for the Messages Contents belonging to the specified group
*
* @param MessageId the id of the owner of the groups to return
*/
function QueryMessageContents(String MessageId);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param MessageId the id of the group from which the members were queried
* @param bWasSuccessful whether the operation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryMessageContentsComplete(String MessageId, bool bWasSuccessful, String Error);
/**
* Returns a copy of the Message Contents that belong to the specified MessageId
* Called after QueryMessageContents.
*
* @param MessageId
* @param MessageContents
*/
function bool GetMessageContents(String MessageId, out array<byte> MessageContents);
/**
*Store group in an object in memory instead of having to query the server again for it
*
* @param Message to be placed in the cache
*/
function CacheMessage( McpMessage Message);
/**
*Store group member in an object in memory instead of having to query the server again for it
*
* @param MessageContents Message payload
* @param MessageId to be placed in the cache
*/
function bool CacheMessageContents(const out array<byte> MessageContents, String MessageId);

View File

@ -0,0 +1,691 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for user messages and the factory method
* for creating the registered implementing object
*/
class McpMessageManager extends McpMessageBase
native
config(Engine)
inherits(FTickableObject);
`include(Engine\Classes\HttpStatusCodes.uci)
/** The URL of CreateMessage function on the server */
var config string CreateMessageUrl;
/** The URL of DeleteMessage function on the server */
var config string DeleteMessageUrl;
/** The URL of ListMessages function on the server */
var config string QueryMessagesUrl;
/** The URL of ListMessageContents function on the server */
var config string QueryMessageContentsUrl;
/** The URL of DeleteAllMessages function on the server */
var config string DeleteAllMessagesUrl;
/**
*CompressMessageRequest holds the information needed by the async compression function
*/
struct native McpCompressMessageRequest
{
/** Uncompressed Source Buffer */
var array<byte> SourceBuffer;
/** Bufffer to hold the compressed data; set after compression is finished */
var array<byte> DestBuffer;
/** Size of the final compressed data */
var int OutCompressedSize;
/** Http Request */
var HttpRequestInterface Request;
/** The compression worker that will do the compression */
var native pointer CompressionWorker{FAsyncTask<FCompressAsyncWorker>};
};
/**
*UncompressMessageRequest holds the information needed by async uncompression
*/
struct native McpUncompressMessageRequest
{
/** Id of message that the compressed payload belongs to */
var string MessageId;
/** Compressed Source Buffer */
var array<byte> SourceBuffer;
/** Bufffer to hold the uncompressed data; set after uncompression is finished */
var array<byte> DestBuffer;
/** Size of the final uncompressed data; passed in before compression from the header of the compressed buffer */
var int OutUncompressedSize;
/** The uncompression worker that will do the uncompression */
var native pointer UncompressionWorker{FAsyncTask<FUncompressAsyncWorker>};
};
/** List of McpCompressMessageRequests to be compressed */
var native array<McpCompressMessageRequest> CompressMessageRequests;
/** List of McpUncompressMessageRequests to be compressed */
var native array<McpUncompressMessageRequest> UncompressMessageRequests;
/**
* Called to start the compression process.
* Adds a compression task to the compression queue
*
* @param MessageCompressionType
* @param MessageContent
* @param Request
*/
native function bool StartAsyncCompression(EMcpMessageCompressionType MessageCompressionType, const out array<byte> MessageContent, HttpRequestInterface Request);
/**
* Called to start the uncompression process.
* Adds a compression task to the uncompression queue
*
* @param MessageId
* @param MessageCompressionType
* @param MessageContent
*/
native function bool StartAsyncUncompression(string MessageId, EMcpMessageCompressionType MessageCompressionType, const out array<byte> MessageContent);
/**
* Called once the uncompression job has finished
* - Ensures that uncompressed message contents are cached and the query's final delegate is called
*
* @param bWasSuccessful passes whether or not the uncompression task completed successfully
* @param UncompressedMessageContents
* @param MessageId
*/
event FinishedAsyncUncompression(bool bWasSuccessful, const out array<byte> UncompressedMessageContents, string MessageId)
{
if(!CacheMessageContents(UncompressedMessageContents, MessageId))
{
`Log(`Location("Error Caching Message Contents"));
}
OnQueryMessageContentsComplete(MessageId, bWasSuccessful, "");
}
/**
* Creates the URL and sends the request to create a Message.
* - This updates the message on the server. QueryMessages will need to be
* - run again before GetMessages will reflect the this new message.
* @param ToUniqueUserIds the ids of users to send a message to
* @param FromUniqueUserId of the user who sent the message
* @param FromFriendlyName The friendly name of the user sending the data
* @param MessageType The application specific message type
* @param PushMessage to be sent to user via push notifications
* @param ValidUntil The date until this message is no longer valid (should be deleted)
* @param MessageContents payload of the message
*/
function CreateMessage(
const out array<String> ToUniqueUserIds,
String FromUniqueUserId,
String FromFriendlyName,
String MessageType,
String PushMessage,
String ValidUntil,
const out array<byte> MessageContents)
{
local string Url;
local HttpRequestInterface CreateMessageRequest;
local McpMessage Message;
local string ToUniqueUserIdsStr;
local int Idx;
// Create HttpRequest
CreateMessageRequest = class'HttpFactory'.static.CreateRequest();
if(CreateMessageRequest != none)
{
// create comma delimited list of recipient ids
for (Idx=0; Idx < ToUniqueUserIds.Length; Idx++)
{
ToUniqueUserIdsStr $= ToUniqueUserIds[Idx];
if ((ToUniqueUserIds.Length - Idx) > 1)
{
ToUniqueUserIdsStr $= ",";
}
}
// Fill url out using parameters
Url = GetBaseURL() $ CreateMessageUrl $ GetAppAccessURL() $
"&toUniqueUserIds=" $ ToUniqueUserIdsStr $
"&fromUniqueUserId=" $ FromUniqueUserId $
"&fromFriendlyName=" $ FromFriendlyName $
"&messageType=" $ MessageType $
"&pushMessage=" $ PushMessage $
"&messageCompressionType=" $ CompressionType $
// JS uses DateTo Param
"&validUntil=" $ ValidUntil
;
// Build our web request with the above URL
CreateMessageRequest.SetURL(URL);
CreateMessageRequest.SetHeader("Content-Type","multipart/form-data");
CreateMessageRequest.SetVerb("POST");
CreateMessageRequest.OnProcessRequestComplete = OnCreateMessageRequestComplete;
//check to see if we need compression
if (CompressionType == MMCT_LZO ||
CompressionType == MMCT_ZLIB)
{
if( !StartAsyncCompression(CompressionType, MessageContents, CreateMessageRequest))
{
OnCreateMessageComplete(Message, false, "Failed to Start Async Compression.");
}
}
else
{
CreateMessageRequest.SetContent(MessageContents);
// Call Web Request
if (!CreateMessageRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
`Log(`Location $ " URL is " $ Url);
}
}
}
/**
* Called once the request/response has completed.
* Used to return any errors and notify any registered delegate
*
* @param CreateMessageRequest the request object that was used
* @param HttpResponse the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnCreateMessageRequestComplete(HttpRequestInterface CreateMessageRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local McpMessage CreatedMessage;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none && CreateMessageRequest != none)
{
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_CREATED;
Content = HttpResponse.GetContentAsString();
if ( bWasSuccessful)
{
`log(`Location@"Message created.");
}
else
{
`log(`Location@" CreateMessage query did not return a message.");
}
}
OnCreateMessageComplete(CreatedMessage, bWasSuccessful, Content);
}
/**
* Deletes a Message by MessageId
*
* @param MessageId Id of the group
*/
function DeleteMessage(String MessageId)
{
local string Url;
local HttpRequestInterface DeleteMessageRequest;
// Delete HttpRequest
DeleteMessageRequest = class'HttpFactory'.static.CreateRequest();
if(DeleteMessageRequest != none)
{
// Fill url out using parameters
Url = GetBaseURL() $ DeleteMessageUrl $ GetAppAccessURL() $
"&messageId=" $ MessageId;
// Build our web request with the above URL
DeleteMessageRequest.SetVerb("DELETE");
DeleteMessageRequest.SetURL(URL);
DeleteMessageRequest.OnProcessRequestComplete = OnDeleteMessageRequestComplete;
// call WebRequest
if(!DeleteMessageRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
`Log(`Location $ "URL is " $ Url);
}
}
/**
* Called once the request/response has completed.
* Used to process the response and notify any
* registered delegate
*
* @param OriginalRequest the request object that was used
* @param HttpResponse the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnDeleteMessageRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Content;
local String MessageId;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
if (HttpResponse != none)
{
ResponseCode = HttpResponse.GetResponseCode();
MessageId = HttpResponse.GetURLParameter("MessageId");
ResponseCode = HttpResponse.GetResponseCode();
Content = HttpResponse.GetContentAsString();
}
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
OnDeleteMessageComplete(MessageId, bWasSuccessful, Content);
}
/**
* Queries the backend for the Messages belonging to the supplied UserId
*
* @param ToUniqueUserId the id of the owner of the groups to return
*/
function QueryMessages(String ToUniqueUserId)
{
// Cache one message result set per user
local string Url;
local HttpRequestInterface QueryMessagesRequest;
// List HttpRequest
QueryMessagesRequest = class'HttpFactory'.static.CreateRequest();
if (QueryMessagesRequest != none)
{
//Create URL parameters
Url = GetBaseURL() $ QueryMessagesUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ ToUniqueUserId;
// Build our web request with the above URL
QueryMessagesRequest.SetURL(URL);
QueryMessagesRequest.SetVerb("GET");
QueryMessagesRequest.OnProcessRequestComplete = OnQueryMessagesRequestComplete;
`Log(`Location@"URL: " $ URL);
// Call WebRequest
if(!QueryMessagesRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param OriginalRequest the request object that was used
* @param HttpResponse the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnQueryMessagesRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local string Error;
local string JsonString;
local JsonObject ParsedJson;
local int JsonIndex;
local McpMessage Message;
local string MessageCompressionTypeString;
// Set default response code
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
// Both HttpResponse and OriginalRequest need to be present
if (HttpResponse != none && OriginalRequest != none)
{
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
if (bWasSuccessful)
{
JsonString = HttpResponse.GetContentAsString();
if (JsonString != "")
{
// @todo joeg - Replace with Wes' ImportJson() once it's implemented
// Parse the json
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
// Add each mapping in the json packet if missing
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
Message.MessageId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("message_id");
Message.ToUniqueUserId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("to_unique_user_id");
Message.FromUniqueUserId = ParsedJson.ObjectArray[JsonIndex].GetStringValue("from_unique_user_id");
Message.FromFriendlyName = ParsedJson.ObjectArray[JsonIndex].GetStringValue("from_friendly_name");
Message.MessageType = ParsedJson.ObjectArray[JsonIndex].GetStringValue("message_type");
MessageCompressionTypeString = ParsedJson.ObjectArray[JsonIndex].GetStringValue("message_compression_type");
Message.ValidUntil = ParsedJson.ObjectArray[JsonIndex].GetStringValue("valid_until");
// Convert CompressionTypeString stored in the datastore to the enum used on the client
switch(MessageCompressionTypeString)
{
case "MMCT_LZO":
Message.MessageCompressionType = MMCT_LZO;
break;
case "MMCT_ZLIB":
Message.MessageCompressionType = MMCT_ZLIB;
break;
default:
Message.MessageCompressionType = MMCT_NONE;
}
CacheMessage(Message);
}
}
else
{
Error = "Query did not return any content in it's response.";
`log(`Location $ Error);
}
}
else
{
Error = HttpResponse.GetContentAsString();
}
}
OnQueryMessagesComplete(Message.ToUniqueUserId, bWasSuccessful, Error);
}
/**
* Returns the set of messages that sent to the specified UserId
* Called after QueryMessages.
*
* @param ToUniqueUserId the user the messages were sent to
* @param MessageList collection of messages
*/
function GetMessageList(string ToUniqueUserId, out McpMessageList MessageList)
{
local int MessageListIndex;
// Check Cache Variable MessageLists
MessageListIndex = MessageLists.Find('ToUniqueUserId', ToUniqueUserId);
if(MessageListIndex != INDEX_NONE)
{
MessageList = MessageLists[MessageListIndex];
}
else
{
`Log(`Location $ " Requester Id not found or MessageLists is empty. Using ToUniqueUserId: " $ ToUniqueUserId);
}
}
/**
* Queries the back-end for the Messages Contents belonging to the specified group
*
* @param MessageId the id of the message to return
*/
function QueryMessageContents(String MessageId)
{
// Cache one message result set per user
local string Url;
local HttpRequestInterface QueryMessageContentsRequest;
// List HttpRequest
QueryMessageContentsRequest = class'HttpFactory'.static.CreateRequest();
if (QueryMessageContentsRequest != none)
{
//Create URL parameters
Url = GetBaseURL() $ QueryMessageContentsUrl $ GetAppAccessURL() $
"&messageId=" $ MessageId;
// Build our web request with the above URL
QueryMessageContentsRequest.SetURL(URL);
QueryMessageContentsRequest.SetVerb("GET");
QueryMessageContentsRequest.OnProcessRequestComplete = OnQueryMessageContentsRequestComplete;
`Log(`Location@"URL: " $ URL);
// Call WebRequest
if(!QueryMessageContentsRequest.ProcessRequest())
{
`Log(`Location@"Failed to process web request for URL(" $ Url $ ")");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param OriginalRequest the request object that was used
* @param HttpResponse the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnQueryMessageContentsRequestComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface HttpResponse, bool bWasSuccessful)
{
local int ResponseCode;
local array<byte> MessageContents;
local string MessageId;
local McpMessage Message;
ResponseCode = `HTTP_STATUS_SERVER_ERROR;
// Both HttpResponse and OriginalRequest need to be present
if (HttpResponse != none && OriginalRequest != none)
{
MessageId = OriginalRequest.GetURLParameter("messageId");
ResponseCode = HttpResponse.GetResponseCode();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == `HTTP_STATUS_OK;
if (bWasSuccessful && Len(MessageId) > 0 )
{
HttpResponse.GetContent(MessageContents);
`log("MessageId:" $ MessageId $" Compressed Message Contents Length:" $ MessageContents.Length);
if (MessageContents.Length > 0)
{
GetMessageById(MessageId, Message);
if( Message.MessageCompressionType == MMCT_NONE)
{
CacheMessageContents(MessageContents, MessageId);
OnQueryMessageContentsComplete(MessageId, bWasSuccessful, HttpResponse.GetContentAsString());
}
else
{
if( !StartAsyncUncompression(MessageId, Message.MessageCompressionType, MessageContents ))
{
OnQueryMessageContentsComplete(MessageId, false, "Could not Start AsyncDecompression");
}
// this will call OnQueryMessageContentsComplete
}
}
else
{
OnQueryMessageContentsComplete(MessageId, false, "Query did not return any content in it's response.");
}
}
else
{
OnQueryMessageContentsComplete(MessageId, false, HttpResponse.GetContentAsString());
}
}
else
{
OnQueryMessageContentsComplete(MessageId, false, "There was No HttpResponse or Request");
}
}
/**
* Returns a copy of the Message Contents that belong to the specified MessageId
* Called after QueryMessageContents.
*
* @param MessageId
* @param MessageContents
*/
function bool GetMessageContents(String MessageId, out array<byte> MessageContents)
{
local bool bWasSuccessful;
local int MessageContentsIndex;
MessageContentsIndex = MessageContentsList.Find('MessageId', MessageId);
if (MessageContentsIndex != INDEX_NONE)
{
MessageContents = MessageContentsList[MessageContentsIndex].MessageContents;
bWasSuccessful = true;
}else
{
bWasSuccessful = false;
}
return bWasSuccessful;
}
/**
*Store group in an object in memory instead of having to query the server again for it
*
* @param Message to be placed in the cache
*/
function CacheMessage( McpMessage Message)
{
local int AddAt;
local int MessageIndex;
local int MessageListIndex;
local McpMessageList UserMessageList;
local bool bWasFound;
//Find the user's cached message list in collection of user's message lists, MessageLists
bWasFound = false;
MessageListIndex = MessageLists.Find('ToUniqueUserId', Message.ToUniqueUserId);
if(MessageListIndex != INDEX_NONE)
{
UserMessageList = MessageLists[MessageListIndex];
// Search the array for any existing adding only when missing
for (MessageIndex = 0; MessageIndex < UserMessageList.Messages.Length && !bWasFound; MessageIndex++)
{
bWasFound = Message.MessageId == UserMessageList.Messages[MessageIndex].MessageId;
}
// Add this one since it wasn't found
if (!bWasFound)
{
AddAt = UserMessageList.Messages.Length;
UserMessageList.Messages.Length = AddAt + 1;
UserMessageList.Messages[AddAt] = Message;
MessageLists[MessageListIndex] = UserMessageList;
}
`log(`Location $ " MessageId: " $ UserMessageList.Messages[AddAt].MessageId);
}
else
{
// Add User with this first returned group since it wasn't found
AddAt = MessageLists.Length;
MessageLists.Length = AddAt +1;
MessageLists[AddAt].ToUniqueUserId = Message.ToUniqueUserId;
MessageLists[AddAt].Messages[0]=Message;
}
}
/**
* Get Message By Id
*
* @param MessageId
* @param Message
*/
function bool GetMessageById(string MessageId, out McpMessage Message)
{
local int MessageListsSize;
local int MessageListsItr;
local int MessageItr;
MessageListsSize = MessageLists.Length;
// Look at each MessageList (userId:array<McpMessage>) mapping
for(MessageListsItr = 0; MessageListsItr < MessageListsSize; MessageListsItr++)
{
// Look to see if the MessageID Inside each MessageList
for(MessageItr = 0; MessageItr < MessageLists[MessageListsItr].Messages.Length; MessageItr++)
{
if ( MessageLists[MessageListsItr].Messages[MessageItr].MessageId == MessageId )
{
// If it is in the message list set the out variable
Message = MessageLists[MessageListsItr].Messages[MessageItr];
// Return here because messageIds are unique so continuing the loop is pointless
return true;
}
}
}
return false;
}
/**
*Store group member in an object in memory instead of having to query the server again for it
*
* @param MessageContents Message payload
* @param MessageId to be placed in the cache
*/
function bool CacheMessageContents(const out array<byte> MessageContents, String MessageId)
{
local int MessageContentsIndex;
local bool bWasSuccessful;
bWasSuccessful = false;
// Have the variables been passed in properly
if(MessageContents.Length > 0 && Len(MessageId) > 0)
{
MessageContentsIndex = MessageContentsList.Find('MessageId', MessageId);
if (MessageContentsIndex != INDEX_NONE)
{
MessageContentsList[MessageContentsIndex].MessageContents = MessageContents;
bWasSuccessful = true;
}else
{
MessageContentsIndex = MessageContentsList.Length;
MessageContentsList.Length = MessageContentsList.Length+1;
MessageContentsList[MessageContentsIndex].MessageId = MessageId;
MessageContentsList[MessageContentsIndex]. MessageContents = MessageContents;
bWasSuccessful = true;
}
}
else
{
`Log(`Location@" Either the MessageContents or MessageId were not Specified.");
}
return bWasSuccessful;
}
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;
}
/**
* Needs to be overridden by child classes
*
* @param ignored
*/
virtual void Tick(FLOAT);
}

View File

@ -0,0 +1,51 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for requesting UTC time from the server
*/
class McpServerTimeBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config String McpServerTimeClassName;
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpServerTimeBase CreateInstance()
{
local class<McpServerTimeBase> McpServerTimeBaseClass;
local McpServerTimeBase NewInstance;
McpServerTimeBaseClass = class<McpServerTimeBase>(DynamicLoadObject(default.McpServerTimeClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpServerTimeBaseClass != None)
{
NewInstance = new McpServerTimeBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Request current UTC time from the server
*/
function QueryServerTime();
/**
* Called when the time request from the server is complete
*
* @param bWasSuccessful true if server was contacted and a valid result received
* @param DateTimeStr string representing UTC server time (yyyy.MM.dd-HH.mm.ss)
* @param Error string representing the error condition
*/
delegate OnQueryServerTimeComplete(bool bWasSuccessful, string DateTimeStr, string Error);
/**
* Retrieve cached timestamp from last server time query
*
* @return string representation of time (yyyy.MM.dd-HH.mm.ss)
*/
function String GetLastServerTime();

View File

@ -0,0 +1,115 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Implementation of interface for requesting UTC time from the server
*/
class McpServerTimeManager extends McpServerTimeBase;
`include(Engine\Classes\HttpStatusCodes.uci)
/** The class name to use in the factory method to create our instance */
var config String TimeStampUrl;
/** String for the last valid server time response */
var String LastTimeStamp;
/** HTTP request object that is used for the server time query. None when no request is in flight */
var HttpRequestInterface HTTPRequestServerTime;
/**
* Request current UTC time from the server
*/
function QueryServerTime()
{
local string Url,ErrorStr;
local bool bPending;
if (HTTPRequestServerTime == None)
{
HTTPRequestServerTime = class'HttpFactory'.static.CreateRequest();
if (HTTPRequestServerTime != None)
{
Url = GetBaseURL() $ TimeStampUrl $ GetAppAccessURL();
HTTPRequestServerTime.SetURL(Url);
HTTPRequestServerTime.SetVerb("GET");
HTTPRequestServerTime.OnProcessRequestComplete = OnQueryServerTimeHTTPRequestComplete;
if (HTTPRequestServerTime.ProcessRequest())
{
bPending = true;
}
else
{
ErrorStr = "failed to start request, Url="$Url;
`log(`StaticLocation@ErrorStr);
}
}
}
else
{
ErrorStr = "last request is still being processed";
`log(`StaticLocation@ErrorStr);
}
if (!bPending)
{
OnQueryServerTimeComplete(false,"",ErrorStr);
}
}
/**
* Called once the request/response has completed for getting server time from Mcp
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnQueryServerTimeHTTPRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local string TimeStr,ResponseStr,ErrorStr;
local int Idx;
local bool bResult;
HTTPRequestServerTime = None;
if (bWasSuccessful &&
Response != None)
{
if (Response.GetResponseCode() == `HTTP_STATUS_OK)
{
ResponseStr = Response.GetContentAsString();
Idx = InStr(ResponseStr, "Timestamp=");
if (Idx != INDEX_NONE)
{
// Example : TimeFormat="yyyy.MM.dd-HH.mm.ss" Timestamp=2011.10.29-03.19.49
TimeStr = Mid(ResponseStr, Idx);
Idx = InStr(ResponseStr, "=");
TimeStr = Mid(TimeStr, Idx);
// cache last valid time stamp
LastTimeStamp = TimeStr;
bResult = true;
}
}
else
{
ErrorStr = "invalid server response code, status="$Response.GetResponseCode();
`log(`StaticLocation@ErrorStr);
}
}
else
{
ErrorStr = "no response";
`log(`StaticLocation@ErrorStr);
}
OnQueryServerTimeComplete(bResult,TimeStr,ErrorStr);
}
/**
* Retrieve cached timestamp from last server time query
*
* @return string representation of time (yyyy.MM.dd-HH.mm.ss)
*/
function String GetLastServerTime()
{
return LastTimeStamp;
}

View File

@ -0,0 +1,62 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Common functionality for all MCP web service interfaces
*/
class McpServiceBase extends Object
native
config(Engine);
/** Class to load and instantiate for the handling configuration options for MCP services */
var config string McpConfigClassName;
/** Contains all the configuration options common to MCP services. Protocol, URL, access key, etc */
var McpServiceConfig McpConfig;
/* Initialize always called after constructing a new MCP service subclass instance via its factory method */
event Init()
{
local class<McpServiceConfig> McpConfigClass;
`log("Loading McpServerBase McpConfigClass:" $ default.McpConfigClassName);
// load the configuration class and instantiate it
McpConfigClass = class<McpServiceConfig>(DynamicLoadObject(default.McpConfigClassName,class'Class'));
if (McpConfigClass != None)
{
McpConfig = new McpConfigClass;
}
}
/**
* @return Base protocol and domain for communicating with MCP server
*/
function string GetBaseURL()
{
return McpConfig.Protocol $ "://" $ McpConfig.Domain;
}
/**
* @return Access rights for app including title id
*/
function string GetAppAccessURL()
{
return "?appKey=" $ McpConfig.AppKey $ "&appSecret=" $ McpConfig.AppSecret;
}
/**
* Build the URL for auth ticket access
*
* @param McpId user id to build auth access for
*
* @return URL parameters needed for user auth ticket usage
*/
function string GetUserAuthURL(string McpId)
{
local string AuthTicket;
AuthTicket = McpConfig.GetUserAuthTicket(McpId);
if (Len(AuthTicket) > 0)
{
return "&authTicket=" $ AuthTicket;
}
return "";
}

View File

@ -0,0 +1,28 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Base configuration for all MCP web services
*/
class McpServiceConfig extends Object
config(Engine);
/** The protocol information to prefix when building the url (e.g. https) */
var config string Protocol;
/** The domain to prefix when building the url (e.g. localhost:8080) */
var config string Domain;
/** AppKey for access privileges to the online services for the current title. Not config so it can be hidden */
var string AppKey;
/** AppSecret for access privileges to the online services for the current title. Not config so it can be hidden */
var string AppSecret;
/**
* Get the auth ticket for a user
*
* @param McpId user id to get auth access for
*
* @return auth ticket string
*/
function string GetUserAuthTicket(string McpId);

View File

@ -0,0 +1,946 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* User file downloading implementation via web service requests to MCP servers
*/
class McpUserCloudFileDownload extends McpServiceBase
native
config(Engine)
implements(UserCloudFileInterface)
dependson(OnlineSubsystem);
`include(Engine\Classes\HttpStatusCodes.uci);
/** The URL to use when requesting enumeration for list of a user's files */
var config String EnumerateCloudFilesUrl;
/** The URL to use when reading the contents of a file */
var config String ReadCloudFileUrl;
/** The URL to use when writing the contents of a file */
var config String WriteCloudFileUrl;
/** The URL to use when deleting a user cloud file */
var config String DeleteCloudFileUrl;
/** Info about a user file entry as enumerated from Mcp */
struct native McpUserCloudFileInfo extends OnlineSubsystem.EmsFile
{
/** Date/time when file was created on server */
var string CreationDate;
/** Date/time when file was updated on server */
var string LastUpdateDate;
/** Compression type used to encode file. Ie. LZO,GZIP,etc */
var string CompressionType;
};
/** Info for a single user's cloud files */
struct native McpUserCloudFilesEntry
{
/** Id for user owning cloud files */
var string UserId;
/** list of files that have started downloads */
var array<TitleFileWeb> DownloadedFiles;
/** list of files available to download for this user */
var array<McpUserCloudFileInfo> EnumeratedFiles;
/** HTTP request that is in flight for enumerating files */
var HttpRequestInterface HTTPRequestEnumerateFiles;
};
/** List of cloud file requests for all known users */
var private array<McpUserCloudFilesEntry> UserCloudFileRequests;
/** The list of delegates to notify when the user file list enumeration is complete */
var private array<delegate<OnEnumerateUserFilesComplete> > EnumerateUserFilesCompleteDelegates;
/** The list of delegates to notify when a user file read is complete */
var private array<delegate<OnReadUserFileComplete> > ReadUserFileCompleteDelegates;
/** The list of delegates to notify when a user file write is complete */
var private array<delegate<OnWriteUserFileComplete> > WriteUserFileCompleteDelegates;
/** The list of delegates to notify when a user file delete is complete */
var private array<delegate<OnDeleteUserFileComplete> > DeleteUserFileCompleteDelegates;
/**
* Copies the file data into the specified buffer for the specified file
*
* @param UserId User owning the storage
* @param FileName the name of the file to read
* @param FileContents the out buffer to copy the data into
*
* @return true if the data was copied, false otherwise
*/
function bool GetFileContents(string UserId,string FileName,out array<byte> FileContents)
{
local bool bResult;
local int EntryIdx,FileIdx;
// Entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
if (EntryIdx != INDEX_NONE)
{
// Check to see if file has been downloaded
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Find('Filename',FileName);
if (FileIdx != INDEX_NONE &&
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState == OERS_Done)
{
// Copy contents
FileContents = UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Data;
bResult = true;
}
}
return bResult;
}
/**
* Empties the set of downloaded files if possible (no async tasks outstanding)
*
* @param UserId User owning the storage
*
* @return true if they could be deleted, false if they could not
*/
function bool ClearFiles(string UserId)
{
local bool bResult;
local int EntryIdx,FileIdx;
// Entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
if (EntryIdx != INDEX_NONE)
{
// Check to see if there files still pending download
for (FileIdx=0; FileIdx < UserCloudFileRequests[EntryIdx].DownloadedFiles.Length; FileIdx++)
{
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState == OERS_InProgress)
{
return false;
}
}
UserCloudFileRequests[EntryIdx].DownloadedFiles.Length = 0;
bResult = true;
}
return bResult;
}
/**
* Empties the cached data for this file if it is not being downloaded currently
*
* @param UserId User owning the storage
* @param FileName the name of the file to remove from the cache
*
* @return true if it could be deleted, false if it could not
*/
function bool ClearFile(string UserId,string FileName)
{
local bool bResult;
local int EntryIdx,FileIdx;
// Entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
if (EntryIdx != INDEX_NONE)
{
// Check to see if file is still pending download
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Find('Filename',FileName);
if (FileIdx != INDEX_NONE &&
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState != OERS_InProgress)
{
UserCloudFileRequests[EntryIdx].DownloadedFiles.Remove(FileIdx,1);
bResult = true;
}
}
return bResult;
}
/**
* Requests a list of available User files from the network store
*
* @param UserId User owning the storage
*
*/
function EnumerateUserFiles(string UserId)
{
local int EntryIdx;
local string Url;
local bool bPending;
// Find entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
// Add an entry for the user if one doesn't exist
if (EntryIdx == INDEX_NONE)
{
EntryIdx = UserCloudFileRequests.Length;
UserCloudFileRequests.Length = EntryIdx+1;
UserCloudFileRequests[EntryIdx].UserId = UserId;
}
// If HTTPRequestEnumerateFiles already exists then don't need to do anything
if (UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles == None)
{
// Create a new HTTP request
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles = class'HttpFactory'.static.CreateRequest();
if (UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles != None)
{
// build URL for requesting list of files from MCP
Url = GetBaseURL() $ EnumerateCloudFilesUrl $ GetAppAccessURL()
$"&uniqueUserId=" $ UserId;
// Configure the HTTP request and start it
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles.SetURL(Url);
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles.SetVerb("GET");
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles.OnProcessRequestComplete = OnHTTPRequestEnumerateUserFilesComplete;
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles.ProcessRequest();
// kicked off successfully
bPending = true;
}
}
else
{
`log(`location@"User files already being enumerated for"
$" UserId="$UserId);
}
// failed to kick off the request, always call the completion delegate
if (!bPending)
{
CallEnumerateUserFileCompleteDelegates(false,UserId);
}
}
/**
* Called once the HTTP request/response has completed for retrieving a list of ems files for a user
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnHTTPRequestEnumerateUserFilesComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int EntryIdx,JsonIdx;
local string JsonString,UserId;
local JsonObject ParsedJson;
local bool bResult;
// Find entry for the user
EntryIdx = UserCloudFileRequests.Find('HTTPRequestEnumerateFiles',Request);
if (EntryIdx != INDEX_NONE)
{
UserId = UserCloudFileRequests[EntryIdx].UserId;
if (bWasSuccessful &&
Response.GetResponseCode() == `HTTP_STATUS_OK)
{
// Clear out any existing entries
UserCloudFileRequests[EntryIdx].EnumeratedFiles.Length = 0;
JsonString = Response.GetContentAsString();
if (JsonString != "")
{
`log(`location@""
$" JsonString="$JsonString);
// Parse JSON response
ParsedJson = class'JsonObject'.static.DecodeJson(JsonString);
// Fill in the list of enumerated user files
UserCloudFileRequests[EntryIdx].EnumeratedFiles.Length = ParsedJson.ObjectArray.Length;
for (JsonIdx=0; JsonIdx < ParsedJson.ObjectArray.Length; JsonIdx++)
{
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].Filename = ParsedJson.ObjectArray[JsonIdx].GetStringValue("file_name");
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].FileSize = int(ParsedJson.ObjectArray[JsonIdx].GetStringValue("file_size"));
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].DlName = ParsedJson.ObjectArray[JsonIdx].GetStringValue("file_name");
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].CreationDate = ParsedJson.ObjectArray[JsonIdx].GetStringValue("creation_date");
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].LastUpdateDate = ParsedJson.ObjectArray[JsonIdx].GetStringValue("last_update_time");
UserCloudFileRequests[EntryIdx].EnumeratedFiles[JsonIdx].CompressionType = ParsedJson.ObjectArray[JsonIdx].GetStringValue("compression_type");
}
}
bResult = true;
}
else
{
`log(`location@"Failed to enumerate files for"
$" UserId="$UserCloudFileRequests[EntryIdx].UserId
$" URL="$Request.GetURL());
}
// done with the HTTP request so clear it out
UserCloudFileRequests[EntryIdx].HTTPRequestEnumerateFiles = None;
}
// call the completion delegate since the HTTP request completed
CallEnumerateUserFileCompleteDelegates(bResult,UserId);
}
/**
* Delegate fired when the list of files has been returned from the network store
*
* @param bWasSuccessful whether the file list was successful or not
* @param UserId User owning the storage
*
*/
delegate OnEnumerateUserFilesComplete(bool bWasSuccessful,string UserId);
/**
* Calls delegates on file enumeration completion
*/
private function CallEnumerateUserFileCompleteDelegates(bool bWasSuccessful,string UserId)
{
local int Index;
local delegate<OnEnumerateUserFilesComplete> CallDelegate;
// Call the completion delegate for receiving the file list
for (Index=0; Index < EnumerateUserFilesCompleteDelegates.Length; Index++)
{
CallDelegate = EnumerateUserFilesCompleteDelegates[Index];
if (CallDelegate != None)
{
CallDelegate(bWasSuccessful,UserId);
}
}
}
/**
* Adds the delegate to the list to be notified when all files have been enumerated
*
* @param EnumerateUserFileCompleteDelegate the delegate to add
*
*/
function AddEnumerateUserFileCompleteDelegate(delegate<OnEnumerateUserFilesComplete> EnumerateUserFileCompleteDelegate)
{
if (EnumerateUserFilesCompleteDelegates.Find(EnumerateUserFileCompleteDelegate) == INDEX_NONE)
{
EnumerateUserFilesCompleteDelegates.AddItem(EnumerateUserFileCompleteDelegate);
}
}
/**
* Removes the delegate from the notify list
*
* @param EnumerateUserFileCompleteDelegate the delegate to remove
*
*/
function ClearEnumerateUserFileCompleteDelegate(delegate<OnEnumerateUserFilesComplete> EnumerateUserFileCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = EnumerateUserFilesCompleteDelegates.Find(EnumerateUserFileCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
EnumerateUserFilesCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Returns the list of User files that was returned by the network store
*
* @param UserId User owning the storage
* @param UserFiles out array of file metadata
*
*/
function GetUserFileList(string UserId,out array<EmsFile> UserFiles)
{
local int EntryIdx,FileIdx;
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
if (EntryIdx != INDEX_NONE)
{
UserFiles.Length = UserCloudFileRequests[EntryIdx].EnumeratedFiles.Length;
for (FileIdx=0; FileIdx < UserCloudFileRequests[EntryIdx].EnumeratedFiles.Length; FileIdx++)
{
UserFiles[FileIdx].DLName = UserCloudFileRequests[EntryIdx].EnumeratedFiles[FileIdx].DLName;
UserFiles[FileIdx].Filename = UserCloudFileRequests[EntryIdx].EnumeratedFiles[FileIdx].Filename;
UserFiles[FileIdx].FileSize = UserCloudFileRequests[EntryIdx].EnumeratedFiles[FileIdx].FileSize;
}
}
else
{
UserFiles.Length = 0;
}
}
/**
* Starts an asynchronous read of the specified user file from the network platform's file store
*
* @param UserId User owning the storage
* @param FileToRead the name of the file to read
*
* @return true if the calls starts successfully, false otherwise
*/
function bool ReadUserFile(string UserId,string FileName)
{
local int EntryIdx,FileIdx;
local string Url;
local bool bPending;
if (Len(UserId) > 0 &&
Len(FileName) > 0)
{
// Find entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
// Add an entry for the user if one doesn't exist
if (EntryIdx == INDEX_NONE)
{
EntryIdx = UserCloudFileRequests.Length;
UserCloudFileRequests.Length = EntryIdx+1;
UserCloudFileRequests[EntryIdx].UserId = UserId;
}
// Find existing file entry
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Find('Filename',FileName);
// Add file entry if one doesn't exist
if (FileIdx == INDEX_NONE)
{
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Length;
UserCloudFileRequests[EntryIdx].DownloadedFiles.Length = FileIdx+1;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename = FileName;
}
// Make sure there is no operation already occurring for the file
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState != OERS_InProgress &&
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest == None)
{
// Update entry for the file being read
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_InProgress;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Data.Length = 0;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest != None)
{
// build URL for writing a single file to user's cloud storage
Url = GetBaseURL() $ ReadCloudFileUrl $ GetAppAccessURL()
$"&uniqueUserId=" $ UserId
$"&fileName=" $ FileName;
// Configure the HTTP request and start it
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetURL(Url);
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetVerb("GET");
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.OnProcessRequestComplete = OnHTTPRequestReadUserFileComplete;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.ProcessRequest();
// kicked off successfully
bPending = true;
}
}
else
{
`log(`location@"File operation already in progress"
$" UserId="$UserId
$" FileName="$FileName);
}
}
else
{
`log(`location@"Invalid parameters"
$" UserId="$UserId
$" FileName="$FileName);
}
// failed to kick off the request, always call the completion delegate
if (!bPending)
{
CallReadUserFileCompleteDelegates(false,UserId,FileName);
}
return bPending;
}
/**
* Called once the HTTP request/response has completed for reading a user cloud file
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnHTTPRequestReadUserFileComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int EntryIdx,FileIdx;
local string FileName,UserId;
local bool bResult;
local array<byte> FileContents;
// Find entry for the user/file
GetUserFileIndexForRequest(Request,EntryIdx,FileIdx);
if (EntryIdx != INDEX_NONE &&
FileIdx != INDEX_NONE)
{
UserId = UserCloudFileRequests[EntryIdx].UserId;
FileName = UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename;
// check for valid response for the request
if (bWasSuccessful &&
Response != None &&
Response.GetResponseCode() == `HTTP_STATUS_OK)
{
// copy contents
Response.GetContent(FileContents);
// and, copy again
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Data = FileContents;
// mark file entry as successfully completed
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_Done;
bResult = true;
}
else
{
// mark file entry as failed
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_Failed;
}
// clear out the request as it's done
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest = None;
}
else
{
`log(`location@"Couldn't find entry index");
}
// call the completion delegate since the HTTP request completed
CallReadUserFileCompleteDelegates(bResult,UserId,FileName);
}
/**
* Delegate fired when a user file read from the network platform's storage is complete
*
* @param bWasSuccessful whether the file read was successful or not
* @param UserId User owning the storage
* @param FileName the name of the file this was for
*
*/
delegate OnReadUserFileComplete(bool bWasSuccessful,string UserId,string FileName);
/**
* Calls delegates on file read completion
*/
private function CallReadUserFileCompleteDelegates(bool bWasSuccessful,string UserId,string FileName)
{
local int Index;
local delegate<OnReadUserFileComplete> CallDelegate;
// Call the completion delegate for receiving the file list
for (Index=0; Index < ReadUserFileCompleteDelegates.Length; Index++)
{
CallDelegate = ReadUserFileCompleteDelegates[Index];
if (CallDelegate != None)
{
CallDelegate(bWasSuccessful,UserId,FileName);
}
}
}
/**
* Adds the delegate to the list to be notified when a requested file has been read
*
* @param ReadUserFileCompleteDelegate the delegate to add
*/
function AddReadUserFileCompleteDelegate(delegate<OnReadUserFileComplete> ReadUserFileCompleteDelegate)
{
if (ReadUserFileCompleteDelegates.Find(ReadUserFileCompleteDelegate) == INDEX_NONE)
{
ReadUserFileCompleteDelegates.AddItem(ReadUserFileCompleteDelegate);
}
}
/**
* Removes the delegate from the notify list
*
* @param ReadUserFileCompleteDelegate the delegate to remove
*/
function ClearReadUserFileCompleteDelegate(delegate<OnReadUserFileComplete> ReadUserFileCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = ReadUserFileCompleteDelegates.Find(ReadUserFileCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
ReadUserFileCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Starts an asynchronous write of the specified user file to the network platform's file store
*
* @param UserId User owning the storage
* @param FileToWrite the name of the file to write
* @param FileContents the out buffer to copy the data into
*
* @return true if the calls starts successfully, false otherwise
*/
function bool WriteUserFile(string UserId,string FileName,const out array<byte> FileContents)
{
local int EntryIdx,FileIdx;
local string Url;
local bool bPending;
if (Len(UserId) > 0 &&
Len(FileName) > 0 &&
FileContents.Length > 0)
{
// Find entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
// Add an entry for the user if one doesn't exist
if (EntryIdx == INDEX_NONE)
{
EntryIdx = UserCloudFileRequests.Length;
UserCloudFileRequests.Length = EntryIdx+1;
UserCloudFileRequests[EntryIdx].UserId = UserId;
}
// Find existing file entry
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Find('Filename',FileName);
// Add file entry if one doesn't exist
if (FileIdx == INDEX_NONE)
{
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Length;
UserCloudFileRequests[EntryIdx].DownloadedFiles.Length = FileIdx+1;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename = FileName;
}
// Make sure there is no operation already occurring for the file
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState != OERS_InProgress &&
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest == None)
{
// Update entry for the file being written
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_InProgress;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Data = FileContents;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest != None)
{
// build URL for writing a single file to user's cloud storage
Url = GetBaseURL() $ WriteCloudFileUrl $ GetAppAccessURL()
$"&uniqueUserId=" $ UserId
$"&fileName=" $ FileName;
// Configure the HTTP request and start it
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetURL(Url);
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetVerb("POST");
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetHeader("Content-Type","multipart/form-data");
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetContent(FileContents);
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.OnProcessRequestComplete = OnHTTPRequestWriteUserFileComplete;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.ProcessRequest();
// kicked off successfully
bPending = true;
}
}
else
{
`log(`location@"File operation already in progress"
$" UserId="$UserId
$" FileName="$FileName);
}
}
else
{
`log(`location@"Invalid parameters"
$" UserId="$UserId
$" FileName="$FileName
$" FileContents="$FileContents.Length);
}
// failed to kick off the request, always call the completion delegate
if (!bPending)
{
CallWriteUserFileCompleteDelegates(false,UserId,FileName);
}
return bPending;
}
/**
* Helper to retrieve the user index and file index corresponding to an HTTP file download request
*
* @param UserIdx [out] entry for the user, -1 if not found
* @param FileIdx [out] entry for the user file, -1 if not found
*/
private function GetUserFileIndexForRequest(HttpRequestInterface Request, out int UserIdx, out int FileIdx)
{
for (UserIdx=0; UserIdx < UserCloudFileRequests.Length; UserIdx++)
{
FileIdx = UserCloudFileRequests[UserIdx].DownloadedFiles.Find('HTTPRequest',Request);
if (FileIdx != INDEX_NONE)
{
break;
}
}
if (FileIdx == INDEX_NONE)
{
UserIdx = INDEX_NONE;
}
}
/**
* Called once the HTTP request/response has completed for writing a user cloud file
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnHTTPRequestWriteUserFileComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int EntryIdx,FileIdx;
local string FileName,UserId;
local bool bResult;
// Find entry for the user/file
GetUserFileIndexForRequest(Request,EntryIdx,FileIdx);
if (EntryIdx != INDEX_NONE &&
FileIdx != INDEX_NONE)
{
UserId = UserCloudFileRequests[EntryIdx].UserId;
FileName = UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename;
// check for valid response for the request
if (bWasSuccessful &&
Response != None &&
Response.GetResponseCode() == `HTTP_STATUS_OK)
{
// mark file entry as successfully completed
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_Done;
bResult = true;
}
else
{
// mark file entry as failed
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_Failed;
}
// clear out the request as it's done
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest = None;
}
else
{
`log(`location@"Couldn't find entry index");
}
// call the completion delegate since the HTTP request completed
CallWriteUserFileCompleteDelegates(bResult,UserId,FileName);
}
/**
* Delegate fired when a user file write to the network platform's storage is complete
*
* @param bWasSuccessful whether the file Write was successful or not
* @param UserId User owning the storage
* @param FileName the name of the file this was for
*
*/
delegate OnWriteUserFileComplete(bool bWasSuccessful,string UserId,string FileName);
/**
* Calls delegates on file write completion
*/
private function CallWriteUserFileCompleteDelegates(bool bWasSuccessful,string UserId,string FileName)
{
local int Index;
local delegate<OnWriteUserFileComplete> CallDelegate;
// Call the completion delegate for receiving the file list
for (Index=0; Index < WriteUserFileCompleteDelegates.Length; Index++)
{
CallDelegate = WriteUserFileCompleteDelegates[Index];
if (CallDelegate != None)
{
CallDelegate(bWasSuccessful,UserId,FileName);
}
}
}
/**
* Adds the delegate to the list to be notified when a requested file has been written
*
* @param WriteUserFileCompleteDelegate the delegate to add
*/
function AddWriteUserFileCompleteDelegate(delegate<OnWriteUserFileComplete> WriteUserFileCompleteDelegate)
{
if (WriteUserFileCompleteDelegates.Find(WriteUserFileCompleteDelegate) == INDEX_NONE)
{
WriteUserFileCompleteDelegates.AddItem(WriteUserFileCompleteDelegate);
}
}
/**
* Removes the delegate from the notify list
*
* @param WriteUserFileCompleteDelegate the delegate to remove
*/
function ClearWriteUserFileCompleteDelegate(delegate<OnWriteUserFileComplete> WriteUserFileCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = WriteUserFileCompleteDelegates.Find(WriteUserFileCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
WriteUserFileCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Starts an asynchronous delete of the specified user file from the network platform's file store
*
* @param UserId User owning the storage
* @param FileToRead the name of the file to read
* @param bShouldCloudDelete whether to delete the file from the cloud
* @param bShouldLocallyDelete whether to delete the file locally
*
* @return true if the calls starts successfully, false otherwise
*/
function bool DeleteUserFile(string UserId,string FileName,bool bShouldCloudDelete,bool bShouldLocallyDelete)
{
local int EntryIdx,FileIdx;
local string Url;
local bool bPending;
if (Len(UserId) > 0 &&
Len(FileName) > 0 &&
bShouldCloudDelete)
{
// Find entry for the user
EntryIdx = UserCloudFileRequests.Find('UserId',UserId);
// Add an entry for the user if one doesn't exist
if (EntryIdx == INDEX_NONE)
{
EntryIdx = UserCloudFileRequests.Length;
UserCloudFileRequests.Length = EntryIdx+1;
UserCloudFileRequests[EntryIdx].UserId = UserId;
}
// Find existing file entry
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Find('Filename',FileName);
// Add file entry if one doesn't exist
if (FileIdx == INDEX_NONE)
{
FileIdx = UserCloudFileRequests[EntryIdx].DownloadedFiles.Length;
UserCloudFileRequests[EntryIdx].DownloadedFiles.Length = FileIdx+1;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename = FileName;
}
// Make sure there is no operation already occurring for the file
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState != OERS_InProgress &&
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest == None)
{
// Update entry for the file being written
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].AsyncState = OERS_InProgress;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Data.Length = 0;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest != None)
{
// build URL for writing a single file to user's cloud storage
Url = GetBaseURL() $ DeleteCloudFileUrl $ GetAppAccessURL()
$"&uniqueUserId=" $ UserId
$"&fileName=" $ FileName;
// Configure the HTTP request and start it
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetURL(Url);
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.SetVerb("DELETE");
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.OnProcessRequestComplete = OnHTTPRequestDeleteUserFileComplete;
UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].HTTPRequest.ProcessRequest();
// kicked off successfully
bPending = true;
}
}
else
{
`log(`location@"File operation already in progress"
$" UserId="$UserId
$" FileName="$FileName);
}
}
else
{
`log(`location@"Invalid parameters"
$" UserId="$UserId
$" FileName="$FileName);
}
// failed to kick off the request, always call the completion delegate
if (!bPending)
{
if (bShouldCloudDelete)
{
CallDeleteUserFileCompleteDelegates(false,UserId,FileName);
}
else if (bShouldLocallyDelete)
{
//@todo - handle local cache deletion
CallDeleteUserFileCompleteDelegates(true,UserId,FileName);
}
}
return bPending;
}
/**
* Called once the HTTP request/response has completed for deleting a user cloud file
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
private function OnHTTPRequestDeleteUserFileComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int EntryIdx,FileIdx;
local string FileName,UserId;
local bool bResult;
// Find entry for the user/file
GetUserFileIndexForRequest(Request,EntryIdx,FileIdx);
if (EntryIdx != INDEX_NONE &&
FileIdx != INDEX_NONE)
{
UserId = UserCloudFileRequests[EntryIdx].UserId;
FileName = UserCloudFileRequests[EntryIdx].DownloadedFiles[FileIdx].Filename;
// check for valid response for the request
if (bWasSuccessful &&
Response != None &&
Response.GetResponseCode() == `HTTP_STATUS_OK)
{
bResult = true;
}
// clear out the file entry
UserCloudFileRequests[EntryIdx].DownloadedFiles.Remove(FileIdx,1);
}
else
{
`log(`location@"Couldn't find entry index");
}
// call the completion delegate since the HTTP request completed
CallDeleteUserFileCompleteDelegates(bResult,UserId,FileName);
}
/**
* Delegate fired when a user file delete from the network platform's storage is complete
*
* @param bWasSuccessful whether the file read was successful or not
* @param UserId User owning the storage
* @param FileName the name of the file this was for
*/
delegate OnDeleteUserFileComplete(bool bWasSuccessful,string UserId,string FileName);
/**
* Calls delegates on file delete completion
*/
private function CallDeleteUserFileCompleteDelegates(bool bWasSuccessful,string UserId,string FileName)
{
local int Index;
local delegate<OnDeleteUserFileComplete> CallDelegate;
// Call the completion delegate for receiving the file list
for (Index=0; Index < DeleteUserFileCompleteDelegates.Length; Index++)
{
CallDelegate = DeleteUserFileCompleteDelegates[Index];
if (CallDelegate != None)
{
CallDelegate(bWasSuccessful,UserId,FileName);
}
}
}
/**
* Adds the delegate to the list to be notified when a requested file has been deleted
*
* @param DeleteUserFileCompleteDelegate the delegate to add
*/
function AddDeleteUserFileCompleteDelegate(delegate<OnDeleteUserFileComplete> DeleteUserFileCompleteDelegate)
{
if (DeleteUserFileCompleteDelegates.Find(DeleteUserFileCompleteDelegate) == INDEX_NONE)
{
DeleteUserFileCompleteDelegates.AddItem(DeleteUserFileCompleteDelegate);
}
}
/**
* Removes the delegate from the notify list
*
* @param DeleteUserFileCompleteDelegate the delegate to remove
*/
function ClearDeleteUserFileCompleteDelegate(delegate<OnDeleteUserFileComplete> DeleteUserFileCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = DeleteUserFileCompleteDelegates.Find(DeleteUserFileCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
DeleteUserFileCompleteDelegates.Remove(RemoveIndex,1);
}
}
/** clears all delegates for e.g. end of level cleanup */
function ClearAllDelegates()
{
EnumerateUserFilesCompleteDelegates.length = 0;
ReadUserFileCompleteDelegates.length = 0;
WriteUserFileCompleteDelegates.length = 0;
DeleteUserFileCompleteDelegates.length = 0;
}

View File

@ -0,0 +1,327 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* This is the base interface for manipulating a user's inventory
*/
class McpUserInventoryBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config string McpUserInventoryClassName;
/**
* Item property which was generated from attribute tables for the item when it was instanced
*/
struct McpInventoryItemAttribute
{
/** The unique id for the attribute */
var string AttributeId;
/** The instanced value for this attribute */
var int Value;
};
/**
* Instanced inventory item aquired via (purchase,earn,etc)
*/
struct McpInventoryItem
{
/** The unique id of the item instance */
var string InstanceItemId;
/** The id of the original template this item was instanced from */
var string GlobalItemId;
/** Total earned amount of this item */
var int Quantity;
/** Total IAP aquired amount of this item */
var int QuantityIAP;
/** Scalar that persists from when the item was instanced */
var float Scalar;
/** The last time (UTC) when this item was modified */
var string LastUpdateTime;
/** Attributes that were generated when instanced to define the item properties */
var array<McpInventoryItemAttribute> Attributes;
};
/**
* Allows for specifying list of global items and quantities
*/
struct McpInventoryItemContainer
{
/** The id of the template item */
var string GlobalItemId;
/** Total amount of this item */
var int Quantity;
};
/**
* Holds a single user's save slot information for inventory items
*/
struct McpInventorySaveSlot
{
/** The owner of this save slot */
var string OwningMcpId;
/** The save slot id */
var string SaveSlotId;
/** The list of inventory items in this save slot */
var array<McpInventoryItem> Items;
};
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpUserInventoryBase CreateInstance()
{
local class<McpUserInventoryBase> McpUserInventoryBaseClass;
local McpUserInventoryBase NewInstance;
McpUserInventoryBaseClass = class<McpUserInventoryBase>(DynamicLoadObject(default.McpUserInventoryClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpUserInventoryBaseClass != None)
{
NewInstance = new McpUserInventoryBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Creates a new save slot for the user
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that is being create
* @param ParentSaveSlotId [optional] if specified then all existing IAP purchases
* from the parent save slot carry over to the newly created save slot
*/
function CreateSaveSlot(string McpId, string SaveSlotId, optional string ParentSaveSlotId);
/**
* Called once save slot creation completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that was created
* @param bWasSuccessful whether the creation succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnCreateSaveSlotComplete(string McpId, string SaveSlotId, bool bWasSuccessful, string Error);
/**
* Deletes an existing user save slot and all the inventory items that belong to it
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that is being deleted
*/
function DeleteSaveSlot(string McpId, string SaveSlotId);
/**
* Called once save slot deletion completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that was deleted
* @param bWasSuccessful whether the deletion succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteSaveSlotComplete(string McpId, string SaveSlotId, bool bWasSuccessful, string Error);
/**
* Query for the list of save slots that exist for the user
*
* @param McpId the id of the user that made the request
*/
function QuerySaveSlotList(string McpId);
/**
* Get the cached list of save slot ids for a user
*
* @param McpId the id of the user that made the request
*
* @return the list of save slot ids for the user
*/
function array<string> GetSaveSlotList(string McpId);
/**
* Called once save slot enumeration query completes
*
* @param McpId the id of the user that made the request
* @param bWasSuccessful whether the deletion succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQuerySaveSlotListComplete(string McpId, bool bWasSuccessful, string Error);
/**
* Query for the list of current inventory items that exist for the user within a save slot
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot to find items for
*/
function QueryInventoryItems(string McpId, string SaveSlotId);
/**
* Called once inventory items enumeration query completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot to find items for
* @param bWasSuccessful whether the deletion succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryInventoryItemsComplete(string McpId, string SaveSlotId, bool bWasSuccessful, string Error);
/**
* Access the currently cached inventory items that have been downloaded for a user's save slot
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot to find items for
* @param OutInventoryItems the list of inventory items that should be filled in
*/
function GetInventoryItems(string McpId, string SaveSlotId, out array<McpInventoryItem> OutInventoryItems);
/**
* Access the currently cached inventory item that have been downloaded for a user's save slot by global id
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot to find items for
* @param InstanceItemId id for the item that is to be found
* @param OutInventoryItem the item that should be filled in
*
* @return true if found
*/
function bool GetInventoryItem(string McpId, string SaveSlotId, string InstanceItemId, out McpInventoryItem OutInventoryItem);
/**
* Purchase item and add it to the user's inventory
* Server determines the transaction is valid
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param GlobalItemId id for the item that is being purchased
* @param PurchaseItemIds list of instance item ids to be used when making the purchase
* @param StoreVersion current known version # of backend store by client
* @param Scalar to associate with the item and pass back to client
*/
function PurchaseItem(string McpId, string SaveSlotId, string GlobalItemId, array<string> PurchaseItemIds, int Quantity, int StoreVersion, float Scalar);
/**
* Called once item purchase completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param GlobalItemId id for the item that is being purchased
* @param UpdatedItemIds list of global item ids for inventory items that were updated
* @param bWasSuccessful whether the item purchase succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnPurchaseItemComplete(string McpId, string SaveSlotId, string GlobalItemId, array<string> UpdatedItemIds, bool bWasSuccessful, string Error);
/**
* Sell item and remove it from the user's inventory
* Server determines the transaction is valid
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being sold
* @param StoreVersion current known version # of backend store by client
* @param ExpectedResultItems optional list of items/quantities that the client expects to receive from the sale
*/
function SellItem(string McpId, string SaveSlotId, string InstanceItemId, int Quantity, int StoreVersion, optional const out array<McpInventoryItemContainer> ExpectedResultItems);
/**
* Called once item sell completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being sold
* @param UpdatedItemIds list of global item ids for inventory items that were updated
* @param bWasSuccessful whether the item sell succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnSellItemComplete(string McpId, string SaveSlotId, string InstanceItemId, array<string> UpdatedItemIds, bool bWasSuccessful, string Error);
/**
* Earn item and add it to the user's inventory
* Server determines the transaction is valid
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param GlobalItemId id for the item that is being earned
* @param Quantity number of the items that were earned
* @param StoreVersion current known version # of backend store by client
*/
function EarnItem(string McpId, string SaveSlotId, string GlobalItemId, int Quantity, int StoreVersion);
/**
* Called once item earn completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param GlobalItemId id for the item that is being earned
* @param UpdatedItemIds list of global item ids for inventory items that were updated
* @param bWasSuccessful whether the item earn succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnEarnItemComplete(string McpId, string SaveSlotId, string GlobalItemId, array<string> UpdatedItemIds, bool bWasSuccessful, string Error);
/**
* Consume item and remove it's consumed quantity from the user's inventory
* Server determines the transaction is valid
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being consumed
* @param Quantity number of the items that were consumed
* @param StoreVersion current known version # of backend store by client
*/
function ConsumeItem(string McpId, string SaveSlotId, string InstanceItemId, int Quantity, int StoreVersion);
/**
* Called once item consume completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being consumed
* @param UpdatedItemIds list of global item ids for inventory items that were updated
* @param bWasSuccessful whether the item consume succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnConsumeItemComplete(string McpId, string SaveSlotId, string InstanceItemId, array<string> UpdatedItemIds, bool bWasSuccessful, string Error);
/**
* Delete item and remove it from the user's inventory
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being deleted
* @param StoreVersion current known version # of backend store by client
*/
function DeleteItem(string McpId, string SaveSlotId, string InstanceItemId, int StoreVersion);
/**
* Called once item delete completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item is in
* @param InstanceItemId id for the item that is being deleted
* @param bWasSuccessful whether the item delete succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteItemComplete(string McpId, string SaveSlotId, string InstanceItemId, bool bWasSuccessful, string Error);
/**
* Record an IAP (In App Purchase) by sending receipt to server for validation
* Results in list of items being added to inventory if successful
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item(s) will be placed in once validated
* @param Receipt IAP receipt to validate the purchase on the server
*/
function RecordIap(string McpId, string SaveSlotId, string Receipt);
/**
* Called once record IAP operation completes
*
* @param McpId the id of the user that made the request
* @param SaveSlotId the save slot that the item(s) will be placed in once validated
* @param UpdatedItemIds list of item ids for inventory items that were updated
* @param bWasSuccessful whether the IAP succeeded and was validated or not
* @param Error string information about the error (if an error)
*/
delegate OnRecordIapComplete(string McpId, string SaveSlotId, array<string> UpdatedItemIds, bool bWasSuccessful, string Error);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,692 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Concrete implementation for mapping McpIds to external account ids
*/
class McpUserManager extends McpUserManagerBase;
/**
* Holds the set of user statuses that were downloaded
*/
var array<McpUserStatus> UserStatuses;
/** The URL to use when making user registration requests to generate an id */
var config String RegisterUserMcpUrl;
/** The URL to use when making user registration requests via Facebook id/token */
var config String RegisterUserFacebookUrl;
/** The URL to use when making user query requests */
var config String QueryUserUrl;
/** The URL to use when making querying for multiple user statuses */
var config String QueryUsersUrl;
/** The URL to use when making user deletion requests */
var config String DeleteUserUrl;
/** The URL to use when making user Facebook authentication requests */
var config String FacebookAuthUrl;
/** The URL to use when making user authentication requests */
var config String McpAuthUrl;
/** Holds the state information for an outstanding user request */
struct UserRequest
{
/** The MCP id that was returned by the backend */
var string McpId;
/** The request object for this request */
var HttpRequestInterface Request;
};
/** The set of add mapping requests that are pending */
var array<HttpRequestInterface> RegisterUserRequests;
/** The set of query requests that are pending */
var array<HttpRequestInterface> QueryUsersRequests;
/** The set of delete requests that are pending */
var array<UserRequest> DeleteUserRequests;
/** The set of delete requests that are pending */
var array<HttpRequestInterface> AuthUserRequests;
/**
* Creates a new user mapped to the UDID that is specified
*/
function RegisterUserGenerated()
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ RegisterUserMcpUrl $ GetAppAccessURL();
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnRegisterUserRequestComplete;
// Store off the data for reporting later
AddAt = RegisterUserRequests.Length;
RegisterUserRequests.Length = AddAt + 1;
RegisterUserRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start RegisterUser web request for URL(" $ Url $ ")");
}
`Log("URL is " $ Url);
}
}
/**
* Maps a newly generated or existing Mcp id to the Facebook id/token requested.
* Note: Facebook id authenticity is verified via the token
*
* @param FacebookId user's FB id to generate Mcp id for
* @param FacebookAuthToken FB auth token obtained by signing in to FB
*/
function RegisterUserFacebook(string FacebookId, string FacebookAuthToken)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ RegisterUserFacebookUrl $ GetAppAccessURL() $
"&facebookId=" $ FacebookId $
"&facebookToken=" $ FacebookAuthToken;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnRegisterUserRequestComplete;
// Store off the data for reporting later
AddAt = RegisterUserRequests.Length;
RegisterUserRequests.Length = AddAt + 1;
RegisterUserRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start RegisterUserFacebook web request for URL(" $ Url $ ")");
}
`Log("URL is " $ Url);
}
}
/**
* Parses the user json
*
* @param JsonPayload the json data to parse
*
* @return the index of the user that was parsed
*/
protected function int ParseUser(String JsonPayload)
{
local JsonObject ParsedJson;
local int UserIndex;
local string McpId;
ParsedJson = class'JsonObject'.static.DecodeJson(JsonPayload);
// If it doesn't have this field, this is bogus JSON
if (ParsedJson.HasKey("unique_user_id"))
{
// Grab the McpId first, since that is our key
McpId = ParsedJson.GetStringValue("unique_user_id");
// See if we already have a user
UserIndex = UserStatuses.Find('McpId', McpId);
if (UserIndex == INDEX_NONE)
{
// Not stored yet, so add one
UserIndex = UserStatuses.Length;
UserStatuses.Length = UserIndex + 1;
UserStatuses[UserIndex].McpId = McpId;
}
// These values are only there for the owner and not friends
if (ParsedJson.HasKey("client_secret"))
{
UserStatuses[UserIndex].SecretKey = ParsedJson.GetStringValue("client_secret");
}
if (ParsedJson.HasKey("ticket"))
{
UserStatuses[UserIndex].Ticket = ParsedJson.GetStringValue("ticket");
}
if (ParsedJson.HasKey("udid"))
{
UserStatuses[UserIndex].UDID = ParsedJson.GetStringValue("udid");
}
// These are always there
UserStatuses[UserIndex].RegisteredDate = ParsedJson.GetStringValue("registered_date");
UserStatuses[UserIndex].LastActiveDate = ParsedJson.GetStringValue("last_active_date");
UserStatuses[UserIndex].DaysInactive = ParsedJson.GetIntValue("days_inactive");
UserStatuses[UserIndex].bIsBanned = ParsedJson.GetBoolValue("is_banned");
}
else
{
UserIndex = INDEX_NONE;
}
// Tell the caller which record was updated
return UserIndex;
}
/**
* Called once the request/response has completed. Used to process the register user result and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnRegisterUserRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local int UserIndex;
local string ResponseString;
local string McpId;
// Search for the corresponding entry in the array
Index = RegisterUserRequests.Find(Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// The response string is the JSON payload for the user
ResponseString = Response.GetContentAsString();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Parse the JSON payload into a user
UserIndex = ParseUser(ResponseString);
if (UserIndex == INDEX_NONE)
{
bWasSuccessful = false;
}
}
McpId = bWasSuccessful ? UserStatuses[UserIndex].McpId : "";
// Notify anyone waiting on this
OnRegisterUserComplete(McpId,
bWasSuccessful,
ResponseString);
`Log("Register user McpId(" $ McpId $ ") was successful " $
bWasSuccessful $ " with ResponseCode(" $ ResponseCode $ ")");
RegisterUserRequests.Remove(Index,1);
}
}
/**
* Authenticates a user is who they claim themselves to be using Facebook as the authority
*
* @param FacebookId the Facebook user that is being authenticated
* @param FacebookToken the secret that authenticates the user
* @param UDID the device id the user is logging in from
*/
function AuthenticateUserFacebook(string FacebookId, string FacebookToken, string UDID)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ FacebookAuthUrl $ GetAppAccessURL() $
"&facebookId=" $ FacebookId $
"&facebookToken=" $ FacebookToken $
"&udid=" $ UDID;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnAuthenticateUserRequestComplete;
// Store off the data for reporting later
AddAt = AuthUserRequests.Length;
AuthUserRequests.Length = AddAt + 1;
AuthUserRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start AuthenticateUserFacebook web request for URL(" $ Url $ ")");
}
}
}
/**
* Authenticates a user is the same as the one that was registered
*
* @param McpId the user that is being authenticated
* @param ClientSecret the secret that authenticates the user
* @param UDID the device id the user is logging in from
*/
function AuthenticateUserMCP(string McpId, string ClientSecret, string UDID)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ McpAuthUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&clientSecret=" $ ClientSecret $
"&udid=" $ UDID;
`log("Started Authenticate, url: "$Url);
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("POST");
Request.OnProcessRequestComplete = OnAuthenticateUserRequestComplete;
// Store off the data for reporting later
AddAt = AuthUserRequests.Length;
AuthUserRequests.Length = AddAt + 1;
AuthUserRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start AuthenticateUserMCP web request for URL(" $ Url $ ")");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnAuthenticateUserRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local int UserIndex;
local string ResponseString;
local string McpId;
local string Ticket;
// Search for the corresponding entry in the array
Index = AuthUserRequests.Find(Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// The response string is the JSON payload for the user
ResponseString = Response.GetContentAsString();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Parse the JSON payload into a user
UserIndex = ParseUser(ResponseString);
if (UserIndex == INDEX_NONE)
{
bWasSuccessful = false;
}
}
// If it was successful, grab the id and ticket for notifying the caller
if (bWasSuccessful)
{
McpId = UserStatuses[UserIndex].McpId;
Ticket = UserStatuses[UserIndex].Ticket;
}
// Notify anyone waiting on this
OnAuthenticateUserComplete(McpId, Ticket, bWasSuccessful, ResponseString);
`Log("Authenticate user was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ") and Ticket ("$Ticket$")");
AuthUserRequests.Remove(Index,1);
}
}
/**
* Queries the backend for the status of a users
*
* @param McpId the id of the user to get the status for
* @param bShouldUpdateLastActive if true, the act of getting the status updates the active time stamp
*/
function QueryUser(string McpId, optional bool bShouldUpdateLastActive)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none && McpId != "")
{
Url = GetBaseURL() $ QueryUserUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId $
"&updateLastActive=" $ bShouldUpdateLastActive;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("GET");
Request.OnProcessRequestComplete = OnQueryUserRequestComplete;
// Store off the data for reporting later
AddAt = QueryUsersRequests.Length;
QueryUsersRequests.Length = AddAt + 1;
QueryUsersRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start QueryUser web request for URL(" $ Url $ ")");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnQueryUserRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local int UserIndex;
local string ResponseString;
// Search for the corresponding entry in the array
Index = QueryUsersRequests.Find(Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// The response string is the JSON payload for the user
ResponseString = Response.GetContentAsString();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Parse the JSON payload into a user
UserIndex = ParseUser(ResponseString);
if (UserIndex == INDEX_NONE)
{
bWasSuccessful = false;
}
}
// Notify anyone waiting on this
OnQueryUsersComplete(bWasSuccessful, ResponseString);
`Log("Query user was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
QueryUsersRequests.Remove(Index,1);
}
}
/**
* Queries the backend for the status of a list of users
*
* @param McpIds the set of ids to get read the status of
*/
function QueryUsers(const out array<String> McpIds)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
local string JsonPayload;
local int Index;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ QueryUsersUrl $ GetAppAccessURL();
// Make a json string from our list of ids
JsonPayload = "[ ";
for (Index = 0; Index < McpIds.Length; Index++)
{
JsonPayload $= "\"" $ McpIds[Index] $ "\"";
// Only add the string if this isn't the last item
if (Index + 1 < McpIds.Length)
{
JsonPayload $= ",";
}
}
JsonPayload $= " ]";
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetContentAsString(JsonPayload);
Request.SetVerb("POST");
Request.SetHeader("Content-Type","multipart/form-data");
Request.OnProcessRequestComplete = OnQueryUsersRequestComplete;
// Store off the data for reporting later
AddAt = QueryUsersRequests.Length;
QueryUsersRequests.Length = AddAt + 1;
QueryUsersRequests[AddAt] = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start QueryUsers web request for URL(" $ Url $ ")");
}
}
}
/**
* Parses the json which contains an array of user data
*
* @param JsonPayload the json data to parse
*
* @return the index of the user that was parsed
*/
protected function ParseUsers(String JsonPayload)
{
local JsonObject ParsedJson;
local int JsonIndex;
local int UserIndex;
local string McpId;
ParsedJson = class'JsonObject'.static.DecodeJson(JsonPayload);
// Parse each user, adding them if needed
for (JsonIndex = 0; JsonIndex < ParsedJson.ObjectArray.Length; JsonIndex++)
{
// If it doesn't have this field, this is bogus JSON
if (ParsedJson.HasKey("unique_user_id"))
{
// Grab the McpId first, since that is our key
McpId = ParsedJson.GetStringValue("unique_user_id");
// See if we already have a user
UserIndex = UserStatuses.Find('McpId', McpId);
if (UserIndex == INDEX_NONE)
{
// Not stored yet, so add one
UserIndex = UserStatuses.Length;
UserStatuses.Length = UserIndex + 1;
UserStatuses[UserIndex].McpId = McpId;
}
// These values are only there for the owner and not friends
if (ParsedJson.HasKey("client_secret"))
{
UserStatuses[UserIndex].SecretKey = ParsedJson.GetStringValue("client_secret");
}
if (ParsedJson.HasKey("ticket"))
{
UserStatuses[UserIndex].Ticket = ParsedJson.GetStringValue("ticket");
}
if (ParsedJson.HasKey("udid"))
{
UserStatuses[UserIndex].UDID = ParsedJson.GetStringValue("udid");
}
// These are always there
UserStatuses[UserIndex].RegisteredDate = ParsedJson.GetStringValue("registered_date");
UserStatuses[UserIndex].LastActiveDate = ParsedJson.GetStringValue("last_active_date");
UserStatuses[UserIndex].DaysInactive = ParsedJson.GetIntValue("days_inactive");
UserStatuses[UserIndex].bIsBanned = ParsedJson.GetBoolValue("is_banned");
}
}
}
/**
* Called once the request/response has completed. Used to process the returned data and notify any
* registered delegate
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnQueryUsersRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int ResponseCode;
local string ResponseString;
// Search for the corresponding entry in the array
Index = QueryUsersRequests.Find(Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// The response string is the JSON payload for the user
ResponseString = Response.GetContentAsString();
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Parse the JSON payload into a user
ParseUsers(ResponseString);
}
// Notify anyone waiting on this
OnQueryUsersComplete(bWasSuccessful, ResponseString);
`Log("Query users was successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
QueryUsersRequests.Remove(Index,1);
}
}
/**
* Returns the set of user statuses queried so far
*
* @param Users the out array that gets the copied data
*/
function GetUsers(out array<McpUserStatus> Users)
{
Users = UserStatuses;
}
/**
* Gets the user status entry for a single user
*
* @param McpId the id of the user that we want to find
* @param User the out result to copy user data
*
* @return true if user was found
*/
function bool GetUser(string McpId, out McpUserStatus User)
{
local int UserIndex;
UserIndex = UserStatuses.Find('McpId', McpId);
if (UserIndex != INDEX_NONE)
{
User = UserStatuses[UserIndex];
return true;
}
return false;
}
/**
* Deletes all data for a user
*
* @param McpId the user that is being expunged from the system
*/
function DeleteUser(string McpId)
{
local String Url;
local HttpRequestInterface Request;
local int AddAt;
Request = class'HttpFactory'.static.CreateRequest();
if (Request != none)
{
Url = GetBaseURL() $ DeleteUserUrl $ GetAppAccessURL() $
"&uniqueUserId=" $ McpId;
// Build our web request with the above URL
Request.SetURL(Url);
Request.SetVerb("DELETE");
Request.OnProcessRequestComplete = OnDeleteUserRequestComplete;
// Store off the data for reporting later
AddAt = DeleteUserRequests.Length;
DeleteUserRequests.Length = AddAt + 1;
DeleteUserRequests[AddAt].McpId = McpId;
DeleteUserRequests[AddAt].Request = Request;
// Now kick off the request
if (!Request.ProcessRequest())
{
`Log("Failed to start DeleteUser web request for URL(" $ Url $ ")");
}
`Log("URL is " $ Url);
}
}
/**
* Called once the delete request completes
*
* @param Request the request object that was used
* @param Response the response object that was generated
* @param bWasSuccessful whether or not the request completed successfully
*/
function OnDeleteUserRequestComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bWasSuccessful)
{
local int Index;
local int UserIndex;
local int ResponseCode;
// Search for the corresponding entry in the array
Index = DeleteUserRequests.Find('Request', Request);
if (Index != INDEX_NONE)
{
ResponseCode = 500;
if (Response != none)
{
ResponseCode = Response.GetResponseCode();
}
// Both of these need to be true for the request to be a success
bWasSuccessful = bWasSuccessful && ResponseCode == 200;
if (bWasSuccessful)
{
// Delete this user from our list
UserIndex = UserStatuses.Find('McpId', DeleteUserRequests[Index].McpId);
if (UserIndex != INDEX_NONE)
{
UserStatuses.Remove(UserIndex, 1);
}
}
// Notify anyone waiting on this
OnDeleteUserComplete(bWasSuccessful,
Response.GetContentAsString());
`Log("Delete user for URL(" $ Request.GetURL() $ ") successful " $ bWasSuccessful $
" with ResponseCode(" $ ResponseCode $ ")");
DeleteUserRequests.Remove(Index,1);
}
}

View File

@ -0,0 +1,160 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Provides the interface for registering and querying users
*/
class McpUserManagerBase extends McpServiceBase
abstract
config(Engine);
/** The class name to use in the factory method to create our instance */
var config String McpUserManagerClassName;
/**
* Holds the status information for a MCP user
*/
struct McpUserStatus
{
/** The McpId of the user */
var String McpId;
/** The secret key (private field, owner only) */
var String SecretKey;
/** The auth ticket for this user (private field, owner only) */
var String Ticket;
/** The device id this user was registered on (private field, owner only) */
var string UDID;
/** The date the user was registered on */
var string RegisteredDate;
/** The last activity date for this user */
var string LastActiveDate;
/** The number of days inactive */
var int DaysInactive;
/** Whether this user has been banned from playing this game */
var bool bIsBanned;
};
/**
* @return the object that implements this interface or none if missing or failed to create/load
*/
final static function McpUserManagerBase CreateInstance()
{
local class<McpUserManagerBase> McpUserManagerBaseClass;
local McpUserManagerBase NewInstance;
McpUserManagerBaseClass = class<McpUserManagerBase>(DynamicLoadObject(default.McpUserManagerClassName,class'Class'));
// If the class was loaded successfully, create a new instance of it
if (McpUserManagerBaseClass != None)
{
NewInstance = new McpUserManagerBaseClass;
NewInstance.Init();
}
return NewInstance;
}
/**
* Creates a new user
*/
function RegisterUserGenerated();
/**
* Maps a newly generated or existing Mcp id to the Facebook id/token requested.
* Note: Facebook id authenticity is verified via the token
*
* @param FacebookId user's FB id to generate Mcp id for
* @param UDID the UDID for the device
* @param FacebookAuthToken FB auth token obtained by signing in to FB
*/
function RegisterUserFacebook(string FacebookId, string FacebookAuthToken);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that was just created
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnRegisterUserComplete(string McpId, bool bWasSuccessful, String Error);
/**
* Authenticates a user is who they claim themselves to be using Facebook as the authority
*
* @param FacebookId the Facebook user that is being authenticated
* @param FacebookToken the secret that authenticates the user
* @param UDID the device id the user is logging in from
*/
function AuthenticateUserFacebook(string FacebookId, string FacebookToken, string UDID);
/**
* Authenticates a user is the same as the one that was registered
*
* @param McpId the user that is being authenticated
* @param ClientSecret the secret that authenticates the user
* @param UDID the device id the user is logging in from
*/
function AuthenticateUserMcp(string McpId, string ClientSecret, string UDID);
/**
* Called once the results come back from the server to indicate success/failure of the operation
*
* @param McpId the id of the user that was just authenticated
* @param Token the security token to use for subsequent user based calls
* @param bWasSuccessful whether the mapping succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnAuthenticateUserComplete(string McpId, string Token, bool bWasSuccessful, String Error);
/**
* Queries the backend for the status of a users
*
* @param McpId the id of the user to get the status for
* @param bShouldUpdateLastActive if true, the act of getting the status updates the active time stamp
*/
function QueryUser(string McpId, optional bool bShouldUpdateLastActive);
/**
* Queries the backend for the status of a list of users
*
* @param McpIds the set of ids to get read the status of
*/
function QueryUsers(const out array<String> McpIds);
/**
* Called once the query results come back from the server to indicate success/failure of the request
*
* @param bWasSuccessful whether the query succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnQueryUsersComplete(bool bWasSuccessful, String Error);
/**
* Returns the set of user statuses queried so far
*
* @param Users the out array that gets the copied data
*/
function GetUsers(out array<McpUserStatus> Users);
/**
* Gets the user status entry for a single user
*
* @param McpId the id of the user that we want to find
* @param User the out result to copy user data
*
* @return true if user was found
*/
function bool GetUser(string McpId, out McpUserStatus User);
/**
* Deletes all data for a user
*
* @param McpId the user that is being expunged from the system
*/
function DeleteUser(string McpId);
/**
* Called once the delete request completes
*
* @param bWasSuccessful whether the request succeeded or not
* @param Error string information about the error (if an error)
*/
delegate OnDeleteUserComplete(bool bWasSuccessful, String Error);

258
IpDrv/Classes/MeshBeacon.uc Normal file
View File

@ -0,0 +1,258 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is the base class for the client/host mesh beacon classes.
*/
class MeshBeacon extends Object
native
inherits(FTickableObject)
config(Engine);
/** Packet ids used to communicate between host & client mesh beacons */
enum EMeshBeaconPacketType
{
// 0 packet type treated as undefined
MB_Packet_UnknownType,
// Sent by a client when a new connection is established and to send history from past bandwidth tests
MB_Packet_ClientNewConnectionRequest,
// Sent by a client when starting a bandwidth test to the host. Immediately followed by dummy buffer based on test size
MB_Packet_ClientBeginBandwidthTest,
// Sent by a client in response to a host request to create a new session
MB_Packet_ClientCreateNewSessionResponse,
// Sent by the host to acknowledge it received the connection request with bandwidth history from a client
MB_Packet_HostNewConnectionResponse,
// Sent by the host to a client to request a new bandwidth test
MB_Packet_HostBandwidthTestRequest,
// Sent by the host to a client that had initiated the bandwidth test, followed by the results of the test
MB_Packet_HostCompletedBandwidthTest,
// Sent by the host to all clients to initiate travel to a new session
MB_Packet_HostTravelRequest,
// Sent by the host to a client to request a new session to be created on the client. A list of players is sent to register with the new session.
MB_Packet_HostCreateNewSessionRequest,
// Used to flag dummy buffers sent for bandwidth testing
MB_Packet_DummyData,
// Sent periodically to tell the host/client that the other end is there
MB_Packet_Heartbeat
};
/** Result of a new client connection request */
enum EMeshBeaconConnectionResult
{
// Client was able to connect successfully
MB_ConnectionResult_Succeeded,
// Client already has a connection, duplicate
MB_ConnectionResult_Duplicate,
// Client connection request failed due to timeout
MB_ConnectionResult_Timeout,
// Client connection request failed due to socket error
MB_ConnectionResult_Error
};
/** State of current bandwidth testing on a client connection */
enum EMeshBeaconBandwidthTestState
{
// Bandwidth test for a connection has not been started/completed yet
MB_BandwidthTestState_NotStarted,
// Bandwidth test has been requested for the connection but the start request hasn't been to the client yet
MB_BandwidthTestState_RequestPending,
// Start request has been sent to client from host but test hasn't been started by client yet
MB_BandwidthTestState_StartPending,
// Test has been started for client and is currently in progress
MB_BandwidthTestState_InProgress,
// Test has completed for the client successfully
MB_BandwidthTestState_Completed,
// Test never completely finished, but didn't error either. Bandwidth results based on incomplete data
MB_BandwidthTestState_Incomplete,
// Test never finished due to timeout waiting for client.
MB_BandwidthTestState_Timeout,
// Test was started but never completed due to error
MB_BandwidthTestState_Error
};
/** Result of a bandwidth test between host/clietn connection */
enum EMeshBeaconBandwidthTestResult
{
// Test has completed for the client successfully
MB_BandwidthTestResult_Succeeded,
// Test never finished due to timeout waiting for client.
MB_BandwidthTestResult_Timeout,
// Test was started but never completed due to error
MB_BandwidthTestResult_Error
};
/** Bandwidth tests that are supported */
enum EMeshBeaconBandwidthTestType
{
// Test for rate at which data can be uploaded
MB_BandwidthTestType_Upstream,
// Test for rate at which data can be downloaded
MB_BandwidthTestType_Downstream,
// Test for time it takes to send/receive a packet
MB_BandwidthTestType_RoundtripLatency,
};
/** Bandwidth data for a connection */
struct native ConnectionBandwidthStats
{
/** Upstream rate in bytes per second */
var int UpstreamRate;
/** Downstream rate in bytes per second */
var int DownstreamRate;
/** Roundtrip latency in milliseconds */
var int RoundtripLatency;
structcpptext
{
/** Constructors */
FConnectionBandwidthStats() {}
FConnectionBandwidthStats(EEventParm)
{
appMemzero(this, sizeof(FConnectionBandwidthStats));
}
/**
* Serialize from NBO buffer to FConnectionBandwidthStats
*/
friend FNboSerializeFromBuffer& operator>>(FNboSerializeFromBuffer& Ar,FConnectionBandwidthStats& BandwidthStats);
/**
* Serialize from FConnectionBandwidthStats to NBO buffer
*/
friend FNboSerializeToBuffer& operator<<(FNboSerializeToBuffer& Ar,const FConnectionBandwidthStats& BandwidthStats);
}
};
/** Player that is to be a member of a new session */
struct native PlayerMember
{
/** The team the player is on */
var int TeamNum;
/** The skill rating of the player */
var int Skill;
/** The unique net id for the player */
var UniqueNetId NetId;
structcpptext
{
/** Constructors */
FPlayerMember() {}
FPlayerMember(EEventParm)
{
appMemzero(this, sizeof(FPlayerMember));
}
/**
* Serialize from NBO buffer to FPlayerMember
*/
friend FNboSerializeFromBuffer& operator>>(FNboSerializeFromBuffer& Ar,FPlayerMember& PlayerEntry);
/**
* Serialize from FPlayerMember to NBO buffer
*/
friend FNboSerializeToBuffer& operator<<(FNboSerializeToBuffer& Ar,const FPlayerMember& PlayerEntry);
}
};
/** The port that the mesh beacon will listen on */
var config int MeshBeaconPort;
/** The object that is used to send/receive data with the remote host/client */
var native transient pointer Socket{FSocket};
/** Used to determine whether to use deferred destruction or not */
var transient bool bIsInTick;
/** The maximum amount of time to pass between heartbeat packets being sent */
var config float HeartbeatTimeout;
/** The elapsed time that has passed since the last heartbeat */
var float ElapsedHeartbeatTime;
/** True if the beacon should be destroyed at the end of the tick */
var transient bool bWantsDeferredDestroy;
/** Whether to the socket(s) or not (not during travel) */
var bool bShouldTick;
/** The name to use when logging (helps debugging) */
var name BeaconName;
/** Size of socket send buffer. Once this is filled then socket blocks on the next send. */
var config int SocketSendBufferSize;
/** Size of socket recv buffer. Once this is filled then socket blocks on the next recv. */
var config int SocketReceiveBufferSize;
/** Maximum size of data that is allowed to be sent for bandwidth testing */
var config int MaxBandwidthTestBufferSize;
/** Minimum size of data that is required to be sent for acurate bandwidth testing */
var config int MinBandwidthTestBufferSize;
/** Maximum time allowed to send the buffer for bandwidth testing */
var config float MaxBandwidthTestSendTime;
/** Maximum time allowed to receive the buffer for bandwidth testing */
var config float MaxBandwidthTestReceiveTime;
/** Maximum number of entries allowed for the bandwidth history of a client connection */
var config int MaxBandwidthHistoryEntries;
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;
}
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Sends a heartbeat packet to the specified socket
*
* @param Socket the socket to send the data on
*
* @return TRUE if it sent ok, FALSE if there was an error
*/
UBOOL SendHeartbeat(FSocket* Socket);
/**
* Handles dummy packets that are received by reading from the buffer until there is no more data or a non-dummy packet is seen.
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessDummyPackets(FNboSerializeFromBuffer& FromBuffer);
}
/**
* Stops listening for requests/responses and releases any allocated memory
*/
native event DestroyBeacon();
defaultproperties
{
bShouldTick=true
}

View File

@ -0,0 +1,373 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is used to connect to a host mesh beacon in order to
* establish a connected mesh network.
*/
class MeshBeaconClient extends MeshBeacon
native;
/**
* Holds a reference to the data that is used to reach the potential host
* while a connection is being established for this client
*/
var const OnlineGameSearchResult HostPendingRequest;
/** Used to send the initial client connection request to the host */
struct native ClientConnectionRequest
{
/** Net Id of primary player on this client */
var UniqueNetId PlayerNetId;
/** NAT Type for this client */
var ENatType NatType;
/** TRUE if the client is able to host a vs match */
var bool bCanHostVs;
/** Ratio of successful vs unsuccessful matches hosted by this client in the past */
var float GoodHostRatio;
/** History of bandwidth results from previous tests. Saved/loaded in the player's profile */
var array<ConnectionBandwidthStats> BandwidthHistory;
/** Elapsed time in minutes since the last bandwidth test */
var int MinutesSinceLastTest;
};
/** Active connection request that is pending for this client */
var const ClientConnectionRequest ClientPendingRequest;
/** Keeps track of all data needed for the current upstream bandwidth test */
struct native ClientBandwidthTestData
{
/** Type of test current being done */
var EMeshBeaconBandwidthTestType TestType;
/** State of the bandwidth test for the client */
var EMeshBeaconBandwidthTestState CurrentState;
/** Total bytes expected to be sent in order to complete this test */
var int NumBytesToSendTotal;
/** Tally of bytes that have been sent so far for the test */
var int NumBytesSentTotal;
/** Size of last buffer that was sent for the test */
var int NumBytesSentLast;
/** Time since test was started */
var float ElapsedTestTime;
};
/** The upstream test state for the client */
var ClientBandwidthTestData CurrentBandwidthTest;
/** Used to drive the client state machine */
enum EMeshBeaconClientState
{
// Inactive or unknown
MBCS_None,
// A connection request is outstanding with host
MBCS_Connecting,
// Connected to the host and is ready to send
MBCS_Connected,
// Failed to establish a connection
MBCS_ConnectionFailed,
// Client has sent to the host and is awaiting for replies
MBCS_AwaitingResponse,
// The client has closed the connection
MBCS_Closed
};
/** The state of the client beacon as it establishes a connection to the host */
var EMeshBeaconClientState ClientBeaconState;
/** The pending request to be sent */
var EMeshBeaconPacketType ClientBeaconRequestType;
/** Indicates how long the client should wait for a connection response before timing out */
var config float ConnectionRequestTimeout;
/** Used to track how long we've been waiting for a connection response */
var float ConnectionRequestElapsedTime;
/** Name of the class to use for address resolving and registering */
var config string ResolverClassName;
/** Class to use for address resolving and registering */
var class<ClientBeaconAddressResolver> ResolverClass;
/** Platform specific address resolver for this beacon. Instantiated using the ResolverClass type. */
var ClientBeaconAddressResolver Resolver;
/** TRUE if address was registered with the beacon address resolver */
var transient bool bUsingRegisteredAddr;
cpptext
{
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Loads the class specified for the Resolver and constructs it if needed
*/
void InitResolver(void);
/**
* Creates a beacon that will send requests to remote hosts
*
* @param Addr the address that we are connecting to (needs to be resolved)
* @return true if the beacon was created successfully, false otherwise
*/
UBOOL InitClientBeacon(const FInternetIpAddr& Addr);
/**
* Unregisters the address and zeros the members involved to prevent multiple releases
*/
void CleanupAddress(void);
/**
* Handles checking for the transition from connecting to connected (socket established)
*/
void CheckConnectionStatus(void);
/**
* Sends all the data for a new client connection on the host.
* Client data includes the player net id, cient NAT type, and previous bandwidth history.
* Assumes that a connection has successfully been established with the host.
*/
void SendClientConnectionRequest(void);
/**
* Checks the socket for a response from the host and processes if present
*/
void ReadHostData(void);
/**
* Processes a packet that was received from the host
*
* @param Packet the packet that the host sent
* @param PacketSize the size of the packet to process
*/
void ProcessHostPacket(BYTE* Packet,INT PacketSize);
/**
* Routes the response packet received from a host to the correct handler based on its type.
*
* @param HostPacketType packet ID from EMeshBeaconPacketType that represents a host response to this client
* @param FromBuffer the packet serializer to read from
* @return TRUE if the data packet type was processed
*/
UBOOL HandleHostPacketByType(BYTE HostPacketType,FNboSerializeFromBuffer& FromBuffer);
/**
* Common routine for notifying of a timeout trying to talk to host
*/
void ProcessHostTimeout(void);
/**
* Processes a heartbeat update, sends a heartbeat back, and clears the timer
*/
void ProcessHeartbeat(void);
/**
* Update a bandwidth test that is currently in progress for this client.
* All other host packets are ignored until the current test finishes or timeout occurs.
*/
void ProcessInProgressBandwidthTest(void);
/**
* Reads the host response to the client's connection request.
* Triggers a delegate.
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessHostResponseConnectionRequest(FNboSerializeFromBuffer& FromBuffer);
/**
* Handles a new bandwidth test request initiated by the host for this client.
* Triggers a delegate.
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessHostRequestBandwidthTest(FNboSerializeFromBuffer& FromBuffer);
/**
* Handles a host response that all upstream bandwidth data was received by the host.
* Triggers a delegate.
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessHostFinishedBandwidthTest(FNboSerializeFromBuffer& FromBuffer);
/**
* Processes a travel request packet that was received from the host
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessHostTravelRequest(FNboSerializeFromBuffer& FromBuffer);
/**
* Processes a request packet that was received from the host to create a new game session
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessHostCreateNewSessionRequest(FNboSerializeFromBuffer& FromBuffer);
}
/**
* Stops listening for requests/responses and releases any allocated memory
*/
native event DestroyBeacon();
/**
* Request a connection to be established to the remote host. As part of the
* connection request also send the NAT type and bandwidth history data for the client.
* Note this request is async and the results will be sent via the delegate
*
* @param DesiredHost the server that the connection will be made to
* @param ClientRequest the client data that is going to be sendt with the request
* @param bRegisterSecureAddress if TRUE then then key exchange is required to connect with the host
* @return TRUE if the request async task started ok, false if it failed to send
*/
native function bool RequestConnection(const out OnlineGameSearchResult DesiredHost,const out ClientConnectionRequest ClientRequest,bool bRegisterSecureAddress);
/**
* Have this client start a bandwidth test on the connected host by sending a start packet
* and then streaming as many dummy packets as possible before timeout (MaxBandwidthTestSendTime).
*
* @param TestType test to run based on enum of EMeshBeaconBandwidthTestType supported bandwidth test types
* @param TestBufferSize size in bytes of total data to be sent for the bandwidth test
* @return TRUE if the test was successfully started
*/
native function bool BeginBandwidthTest(EMeshBeaconBandwidthTestType TestType,INT TestBufferSize);
/**
* Delegate called by the client mesh beacon when a connection request has been responded to by the destination host
*
* @param ConnectionResult whether the connection request was successful
*/
delegate OnConnectionRequestResult(EMeshBeaconConnectionResult ConnectionResult);
/**
* Delegate called by the client mesh beacon when a new bandwidth test request has been received from the host.
*
* @param TestType test to run based on enum of EMeshBeaconBandwidthTestType supported bandwidth test types
*/
delegate OnReceivedBandwidthTestRequest(EMeshBeaconBandwidthTestType TestType);
/**
* Delegate called by the client mesh beacon when bandwidth testing has completed on the host
* and the results have been sent back to this client.
*
* @param TestType test that completed based on enum of EMeshBeaconBandwidthTestType supported bandwidth test types
* @param TestResult overall result from running the test
* @param BandwidthStats statistics and timing information from running the test
*/
delegate OnReceivedBandwidthTestResults(
EMeshBeaconBandwidthTestType TestType,
EMeshBeaconBandwidthTestResult TestResult,
const out ConnectionBandwidthStats BandwidthStats);
/**
* Delegate called by the client mesh beacon when the host sends a request for all clients to travel to
* the destination included in the packet.
*
* @param SessionName the name of the session to register
* @param SearchClass the search that should be populated with the session
* @param PlatformSpecificInfo the binary data to place in the platform specific areas
*/
delegate OnTravelRequestReceived(name SessionName,class<OnlineGameSearch> SearchClass,const out byte PlatformSpecificInfo[80]);
/**
* Delegate called by the client mesh beacon when the host sends a request for a client to create a new game session.
* Used during game session migration to a new host.
*
* @param SessionName the name of the session to register
* @param SearchClass the search that should be populated with the session
* @param Players list of players to register on the newly created session
*/
delegate OnCreateNewSessionRequestReceived(name SessionName,class<OnlineGameSearch> SearchClass,const out array<PlayerMember> Players);
/**
* Notify host of a newly created game session by this client. Host can decide to use/discard the new game session.
*
* @param bSuccess TRUE if the session was created successfully
* @param SessionName the name of the session that was created
* @param SearchClass the search that should be populated with the session
* @param PlatformSpecificInfo the binary data to place in the platform specific areas
*/
native function bool SendHostNewGameSessionResponse(bool bSuccess,name SessionName,class<OnlineGameSearch> SearchClass,const out byte PlatformSpecificInfo[80]);
`if(`notdefined(FINAL_RELEASE))
/**
* Render debug info about the client mesh beacon
*/
function DumpInfo()
{
local int HistoryIdx;
`Log("Debug info for Beacon: "$BeaconName,,'DevBeacon');
`Log("",,'DevBeacon');
`Log("Client entry: ",,'DevBeacon');
`Log(" PlayerNetId: "$class'OnlineSubsystem'.static.UniqueNetIdToString(ClientPendingRequest.PlayerNetId),,'DevBeacon');
`Log(" NatType: "$ClientPendingRequest.NatType,,'DevBeacon');
`Log(" GoodHostRatio: "$ClientPendingRequest.GoodHostRatio,,'DevBeacon');
`Log(" bCanHostVs: "$ClientPendingRequest.bCanHostVs,,'DevBeacon');
`Log(" MinutesSinceLastTest: "$ClientPendingRequest.MinutesSinceLastTest,,'DevBeacon');
`Log(" BandwidthTest.CurrentState: "$CurrentBandwidthTest.CurrentState,,'DevBeacon');
`Log(" BandwidthTest.TestType: "$CurrentBandwidthTest.TestType,,'DevBeacon');
`Log(" Bandwidth History: "$ClientPendingRequest.BandwidthHistory.Length,,'DevBeacon');
for (HistoryIdx=0; HistoryIdx < ClientPendingRequest.BandwidthHistory.Length; HistoryIdx++)
{
`Log(" "
$" Upstream bytes/sec: "$ClientPendingRequest.BandwidthHistory[HistoryIdx].UpstreamRate
$" Downstream bytes/sec: "$ClientPendingRequest.BandwidthHistory[HistoryIdx].DownstreamRate
$" Roundrtrip msec: "$ClientPendingRequest.BandwidthHistory[HistoryIdx].RoundtripLatency,,'DevBeacon');
}
}
/**
* Render debug info about the client mesh beacon
*
* @param Canvas canvas object to use for rendering debug info
*/
function DebugRender(Canvas Canvas)
{
local int HistoryIdx;
local float XL,YL;
local float Offset;
Offset = 50;
Canvas.Font = class'Engine'.Static.GetTinyFont();
Canvas.StrLen("============================================================",XL,YL);
Canvas.SetPos(Offset,Offset);
Canvas.SetDrawColor(0,0,255,64);
Canvas.DrawTile(Canvas.DefaultTexture,XL,Canvas.SizeY-(Offset*2),0,0,1,1);
Canvas.SetPos(Offset,Offset);
Canvas.SetDrawColor(255,255,255);
Canvas.DrawText("Debug info for Beacon: "$BeaconName);
Canvas.DrawText("");
Canvas.DrawText("Client entry: ");
Canvas.StrLen("============================================================",XL,YL);
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
Canvas.DrawText("PlayerNetId: "$class'OnlineSubsystem'.static.UniqueNetIdToString(ClientPendingRequest.PlayerNetId));
Canvas.DrawText("NatType: "$ClientPendingRequest.NatType);
Canvas.DrawText("GoodHostRatio: "$ClientPendingRequest.GoodHostRatio);
Canvas.DrawText("bCanHostVs: "$ClientPendingRequest.bCanHostVs);
Canvas.DrawText("MinutesSinceLastTest: "$ClientPendingRequest.MinutesSinceLastTest);
Canvas.DrawText("Current BandwidthTest: ");
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
Canvas.DrawText("CurrentState: "$CurrentBandwidthTest.CurrentState);
Canvas.DrawText("TestType: "$CurrentBandwidthTest.TestType);
Canvas.DrawText("NumBytesToSendTotal: "$CurrentBandwidthTest.NumBytesToSendTotal);
Canvas.DrawText("NumBytesSentTotal: "$CurrentBandwidthTest.NumBytesSentTotal);
Canvas.SetPos(Canvas.CurX-10,Canvas.CurY);
Canvas.DrawText("Bandwidth History: "$ClientPendingRequest.BandwidthHistory.Length);
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
for (HistoryIdx=0; HistoryIdx < ClientPendingRequest.BandwidthHistory.Length; HistoryIdx++)
{
Canvas.DrawText(" Upstream bytes/sec: "$ClientPendingRequest.BandwidthHistory[HistoryIdx].UpstreamRate
$" Roundrtrip msec: "$ClientPendingRequest.BandwidthHistory[HistoryIdx].RoundtripLatency);
}
}
`endif

View File

@ -0,0 +1,461 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is used to handle connections from client mesh beacons in order to
* establish a mesh network.
*/
class MeshBeaconHost extends MeshBeacon
native;
/** Stats stored for the current bandwidth test on a client connection */
struct native ClientConnectionBandwidthTestData
{
/** Current progress of bandwidth test. Only one client should be MB_BandwidthTestState_InProgress at a time. */
var EMeshBeaconBandwidthTestState CurrentState;
/** Type of bandwidth test currently running */
var EMeshBeaconBandwidthTestType TestType;
/** Total bytes needed to complete the test */
var int BytesTotalNeeded;
/** Total bytes received by the client so far */
var int BytesReceived;
/** Time when request was first sent to client to start the test*/
var double RequestTestStartTime;
/** Time when first response was received from client to being the test */
var double TestStartTime;
/** Resulting stats from the bandwidth test */
var ConnectionBandwidthStats BandwidthStats;
};
/** Holds the information for a client and whether they've timed out */
struct native ClientMeshBeaconConnection
{
/** The unique id of the player for this connection */
var UniqueNetId PlayerNetId;
/** How long it's been since the last heartbeat */
var float ElapsedHeartbeatTime;
/** The socket this client is communicating on */
var native transient pointer Socket{FSocket};
/** True if the client connection has already been accepted for this player */
var bool bConnectionAccepted;
/** Bandwidth test being run for the client */
var ClientConnectionBandwidthTestData BandwidthTest;
/** The NAT of the client as reported by the client */
var ENatType NatType;
/** TRUE if the client is able to host a vs match */
var bool bCanHostVs;
/** Ratio of successful vs unsuccessful matches hosted by this client in the past */
var float GoodHostRatio;
/**
* Previous bandwidth history reported by the client ordered from newest to oldest.
* New bandwidth tests that occur on this host also get added to this history.
*/
var array<ConnectionBandwidthStats> BandwidthHistory;
/** Elapsed time in minutes since the last bandwidth test */
var int MinutesSinceLastTest;
};
/** The object that is used to send/receive data with the remote host/client */
var const array<ClientMeshBeaconConnection> ClientConnections;
/** List of players this beacon is waiting to establish connections to. */
var private array<UniqueNetId> PendingPlayerConnections;
/** Net Id of player that is hosting this beacon */
var const UniqueNetId OwningPlayerId;
/** TRUE if new bandwidth test requests should be handled. Set to false to ignore any pending and new requests. */
var private bool bAllowBandwidthTesting;
/** The number of connections to allow before refusing them */
var config int ConnectionBacklog;
cpptext
{
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Accepts any pending connections and adds them to our queue
*/
void AcceptConnections(void);
/**
* Reads the socket and processes any data from it
*
* @param ClientConn the client connection that sent the packet
* @return TRUE if the socket is ok, FALSE if it is in error
*/
UBOOL ReadClientData(FClientMeshBeaconConnection& ClientConn);
/**
* Processes a packet that was received from a client
*
* @param Packet the packet that the client sent
* @param PacketSize the size of the packet to process
* @param ClientConn the client connection that sent the packet
*/
void ProcessClientPacket(BYTE* Packet,INT PacketSize,FClientMeshBeaconConnection& ClientConn);
/**
* Routes the packet received from a client to the correct handler based on its type.
* Overridden by base implementations to handle custom data packet types
*
* @param ClientPacketType packet ID from EMeshBeaconPacketType (or derived version) that represents a client request
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
* @return TRUE if the requested packet type was processed
*/
UBOOL HandleClientPacketByType(BYTE ClientPacketType,FNboSerializeFromBuffer& FromBuffer,FClientMeshBeaconConnection& ClientConn);
/**
* Read the client data for a new connection request. Includes player ID, NAT type, bandwidth history.
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
void ProcessClientConnectionRequest(FNboSerializeFromBuffer& FromBuffer,FClientMeshBeaconConnection& ClientConn);
/**
* Sends the results of a connection request by the client.
*
* @param ConnectionResult result of the connection request
* @param ClientConn the client connection with socket to send the response on
*/
void SendClientConnectionResponse(EMeshBeaconConnectionResult ConnectionResult,FClientMeshBeaconConnection& ClientConn);
/**
* The client has started sending data for a new bandwidth test.
* Begin measurements for test and process data that is received.
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
void ProcessClientBeginBandwidthTest(FNboSerializeFromBuffer& FromBuffer,FClientMeshBeaconConnection& ClientConn);
/**
* The client currently has a bandwidth test that has been started and is now in progress.
* Process data that is received and handle timeout and finishing the test.
* Only packets of type MB_Packet_DummyData are expected from the client once the test has started.
*
* @param PacketType type of packet read from the buffer
* @param AvailableToRead data still available to read from the buffer
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
void ProcessClientInProgressBandwidthTest(BYTE PacketType,INT AvailableToRead,FNboSerializeFromBuffer& FromBuffer,FClientMeshBeaconConnection& ClientConn);
/**
* Begin processing for a new upstream bandwidth test on a client. All packets
* from the client are expected to be dummy packets from this point until NumBytesBeingSent is
* reached or we hit timeout receiving the data (MaxBandwidthTestReceiveTime).
*
* @param ClientConn the client connection that is sending packets for the test
* @param NumBytesBeingSent expected size of test data being sent in bytes for the bandwidth test to complete
*/
void BeginUpstreamTest(FClientMeshBeaconConnection& ClientConn, INT NumBytesBeingSent);
/**
* Finish process for an in-progress upstream bandwidth test on a client. The test
* is marked as completed successfully if all the expected data for the test was received
* or if the test ended prematurely but there was still enough data (MinBandwidthTestBufferSize)
* to calculate results.
*
* @param ClientConn the client connection that is sending packets for the test
*/
void FinishUpstreamTest(FClientMeshBeaconConnection& ClientConn);
/**
* Sends a request to client to start a new bandwidth test.
*
* @param TestType EMeshBeaconBandwidthTestType type of bandwidth test to request
* @param TestBufferSize size of buffer to use for the test
* @param ClientConn the client connection with socket to send the response on
*/
void SendBandwidthTestStartRequest(BYTE TestType,INT TestBufferSize,FClientMeshBeaconConnection& ClientConn);
/**
* Sends the results of a completed bandwidth test to the client.
*
* @param TestResult result of the bandwidth test
* @param ClientConn the client connection with socket to send the response on
*/
void SendBandwidthTestCompletedResponse(EMeshBeaconBandwidthTestResult TestResult,FClientMeshBeaconConnection& ClientConn);
/**
* The client has create a new game session and has sent the session results back.
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
void ProcessClientCreateNewSessionResponse(FNboSerializeFromBuffer& FromBuffer,FClientMeshBeaconConnection& ClientConn);
}
/**
* Creates a listening host mesh beacon to accept new client connections.
*
* @param InOwningPlayerId Net Id of player that is hosting this beacon
* @return true if the beacon was created successfully, false otherwise
*/
native function bool InitHostBeacon(UniqueNetId InOwningPlayerId);
/**
* Stops listening for clients and releases any allocated memory
*/
native event DestroyBeacon();
/**
* Send a request to a client connection to initiate a new bandwidth test.
*
* @param PlayerNetId player with an active connection to receive test request
* @param TestType EMeshBeaconBandwidthTestType type of bandwidth test to request
* @param TestBufferSize size of buffer in bytes to use for running the test
* @return TRUE if the request was successfully sent to the client
*/
native function bool RequestClientBandwidthTest(UniqueNetId PlayerNetId,EMeshBeaconBandwidthTestType TestType,int TestBufferSize);
/**
* Determine if a client is currently running a bandwidth test.
*
* @return TRUE if a client connection is currently running a bandwidth test
*/
native function bool HasInProgressBandwidthTest();
/**
* Cancel any bandwidth tests that are already in progress.
*/
native function CancelInProgressBandwidthTests();
/**
* Determine if a client is currently waiting/pending for a bandwidth test.
*
* @return TRUE if a client connection is currently pending a bandwidth test
*/
native function bool HasPendingBandwidthTest();
/**
* Cancel any bandwidth tests that are pending.
*/
native function CancelPendingBandwidthTests();
/**
* Enable/disable future bandwidth test requests and current pending tests.
*
* @param bEnabled true to allow bandwidth testing to be processed by the beacon
*/
function AllowBandwidthTesting(bool bEnabled)
{
bAllowBandwidthTesting = bEnabled;
}
/**
* Delegate called by the host mesh beacon after establishing a new client socket and
* receiving the data for a new connection request.
*
* @param NewClientConnection client that sent the request for a new connection
*/
delegate OnReceivedClientConnectionRequest(const out ClientMeshBeaconConnection NewClientConnection);
/**
* Delegate called by the host mesh beacon when bandwidth testing has started for a client connection.
* This occurs only when the client sends the start packet to initiate the test.
*
* @param PlayerNetId net id for player of client connection that started the test
* @param TestType test to run based on enum of EMeshBeaconBandwidthTestType supported bandwidth test types
*/
delegate OnStartedBandwidthTest(UniqueNetId PlayerNetId,EMeshBeaconBandwidthTestType TestType);
/**
* Delegate called by the host mesh beacon when bandwidth testing has completed for a client connection.
* This occurs when the test completes successfully or due to error/timeout.
*
* @param PlayerNetId net id for player of client connection that finished the test
* @param TestType test that completed based on enum of EMeshBeaconBandwidthTestType supported bandwidth test types
* @param TestResult overall result from running the test
* @param BandwidthStats statistics and timing information from running the test
*/
delegate OnFinishedBandwidthTest(
UniqueNetId PlayerNetId,
EMeshBeaconBandwidthTestType TestType,
EMeshBeaconBandwidthTestResult TestResult,
const out ConnectionBandwidthStats BandwidthStats);
/**
* Set list of pending player ids we are waiting to connect with.
* Once all connections are established then the OnAllPendingPlayersConnected delegate is called.
*
* @param Players list of player ids we are waiting to connect
*/
function SetPendingPlayerConnections(const out array<UniqueNetId> Players)
{
PendingPlayerConnections = Players;
}
/**
* Determine if the given player has an active connection on this host beacon.
*
* @param PlayerNetId player we are searching for
* @return index within ClientConnections for the player's connection, -1 if not found
*/
native function int GetConnectionIndexForPlayer(UniqueNetId PlayerNetId);
/**
* Determine if the players all have connections on this host beacon
*
* @param Players list of player ids we are searching for
* @return TRUE if all players had connections
*/
native function bool AllPlayersConnected(const out array<UniqueNetId> Players);
/**
* Delegate called by the host mesh beacon when all players in the PendingPlayerConnections list get connections.
*/
delegate OnAllPendingPlayersConnected();
/**
* Tells all of the clients to go to a specific session (contained in platform
* specific info). Used to route all clients to one destination.
*
* @param SessionName the name of the session to register
* @param SearchClass the search that should be populated with the session
* @param PlatformSpecificInfo the binary data to place in the platform specific areas
*/
native function TellClientsToTravel(name SessionName,class<OnlineGameSearch> SearchClass,const out byte PlatformSpecificInfo[80]);
/**
* Sends a request to a specified client to create a new game session.
*
* @param PlayerNetId net id of player for client connection to send request to
* @param SessionName the name of the session to create
* @param SearchClass the search that should be with corresponding game settings when creating the session
* @param Players list of players to register on the newly created session
*/
native function bool RequestClientCreateNewSession(UniqueNetId PlayerNetId,name SessionName,class<OnlineGameSearch> SearchClass,const out array<PlayerMember> Players);
/**
* Delegate called by the host mesh beacon when it gets the results of a new game session creation on a client.
*
* @param bSucceeded TRUE if the the new session was created on the client
* @param SessionName the name of the session to create
* @param SearchClass the search that should be with corresponding game settings when creating the session
* @param PlatformSpecificInfo the platform specific binary data of the new session
*/
delegate OnReceivedClientCreateNewSessionResult(bool bSucceeded,name SessionName,class<OnlineGameSearch> SearchClass,const out byte PlatformSpecificInfo[80]);
`if(`notdefined(FINAL_RELEASE))
/**
* Logs the all the connected clients of this this beacon
*/
function DumpConnections()
{
local int ClientIdx, HistoryIdx;
local UniqueNetId NetId;
`Log("Debug info for Beacon: "$BeaconName,,'DevBeacon');
for (ClientIdx=0; ClientIdx < ClientConnections.Length; ClientIdx++)
{
NetId = ClientConnections[ClientIdx].PlayerNetId;
`Log("",,'DevBeacon');
`Log("Client connection entry: "$ClientIdx,,'DevBeacon');
`Log(" PlayerNetId: "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId),,'DevBeacon');
`Log(" NatType: "$ClientConnections[ClientIdx].NatType,,'DevBeacon');
`Log(" GoodHostRatio: "$ClientConnections[ClientIdx].GoodHostRatio,,'DevBeacon');
`Log(" bCanHostVs: "$ClientConnections[ClientIdx].bCanHostVs,,'DevBeacon');
`Log(" MinutesSinceLastTest: "$ClientConnections[ClientIdx].MinutesSinceLastTest,,'DevBeacon');
`Log(" BandwidthTest.CurrentState: "$ClientConnections[ClientIdx].BandwidthTest.CurrentState,,'DevBeacon');
`Log(" BandwidthTest.TestType: "$ClientConnections[ClientIdx].BandwidthTest.TestType,,'DevBeacon');
`Log(" Bandwidth History: "$ClientConnections[ClientIdx].BandwidthHistory.Length,,'DevBeacon');
for (HistoryIdx=0; HistoryIdx < ClientConnections[ClientIdx].BandwidthHistory.Length; HistoryIdx++)
{
`Log(" "
$" Upstream bytes/sec: "$ClientConnections[ClientIdx].BandwidthHistory[HistoryIdx].UpstreamRate
$" Downstream bytes/sec: "$ClientConnections[ClientIdx].BandwidthHistory[HistoryIdx].DownstreamRate
$" Roundrtrip msec: "$ClientConnections[ClientIdx].BandwidthHistory[HistoryIdx].RoundtripLatency,,'DevBeacon');
}
}
`Log("");
}
/**
* Render debug info about the client mesh beacon
*
* @param Canvas canvas object to use for rendering debug info
* @param CurOptimalHostId net id of player that should be highlighted as the current optimal host
*/
function DebugRender(Canvas Canvas, UniqueNetId CurOptimalHostId)
{
local int ClientIdx,HistoryIdx;
local UniqueNetId NetId;
local float XL,YL;
local float Offset;
Offset = 50;
Canvas.Font = class'Engine'.Static.GetTinyFont();
Canvas.StrLen("============================================================",XL,YL);
YL = Canvas.SizeY-(Offset*2);
Canvas.SetPos(Offset,Offset);
Canvas.SetDrawColor(0,0,255,64);
Canvas.DrawTile(Canvas.DefaultTexture,XL,YL,0,0,1,1);
Canvas.SetPos(Offset,Offset);
Canvas.SetDrawColor(255,255,255);
Canvas.DrawText("Debug info for Beacon:"$BeaconName);
if (CurOptimalHostId == OwningPlayerId)
{
Canvas.SetDrawColor(255,255,0);
}
Canvas.DrawText("Owning Host: "$class'OnlineSubsystem'.static.UniqueNetIdToString(OwningPlayerId));
for (ClientIdx=0; ClientIdx < ClientConnections.Length; ClientIdx++)
{
Canvas.SetDrawColor(255,255,255);
if (Canvas.CurY >= YL)
{
Canvas.SetPos(Canvas.CurX+XL,Offset);
}
NetId = ClientConnections[ClientIdx].PlayerNetId;
Canvas.DrawText("============================================================");
Canvas.DrawText("Client connection entry: "$ClientIdx);
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
if (CurOptimalHostId == NetId)
{
Canvas.SetDrawColor(255,255,0);
}
Canvas.DrawText("PlayerNetId: "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId));
Canvas.SetDrawColor(255,255,255);
Canvas.DrawText("NatType: "$ClientConnections[ClientIdx].NatType);
Canvas.DrawText("GoodHostRatio: "$ClientConnections[ClientIdx].GoodHostRatio);
Canvas.DrawText("bCanHostVs: "$ClientConnections[ClientIdx].bCanHostVs);
Canvas.DrawText("MinutesSinceLastTest: "$ClientConnections[ClientIdx].MinutesSinceLastTest);
Canvas.DrawText("Current BandwidthTest: ");
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
Canvas.DrawText("CurrentState: "$ClientConnections[ClientIdx].BandwidthTest.CurrentState);
Canvas.DrawText("TestType: "$ClientConnections[ClientIdx].BandwidthTest.TestType);
Canvas.DrawText("BytesTotalNeeded: "$ClientConnections[ClientIdx].BandwidthTest.BytesTotalNeeded);
Canvas.DrawText("BytesReceived: "$ClientConnections[ClientIdx].BandwidthTest.BytesReceived);
Canvas.DrawText("UpstreamRate bytes/sec: "$ClientConnections[ClientIdx].BandwidthTest.BandwidthStats.UpstreamRate);
Canvas.SetPos(Canvas.CurX-10,Canvas.CurY);
Canvas.DrawText("Bandwidth History: "$ClientConnections[ClientIdx].BandwidthHistory.Length);
Canvas.SetPos(Canvas.CurX+10,Canvas.CurY);
for (HistoryIdx=0; HistoryIdx < ClientConnections[ClientIdx].BandwidthHistory.Length; HistoryIdx++)
{
Canvas.DrawText("Upstream bytes/sec: "$ClientConnections[ClientIdx].BandwidthHistory[HistoryIdx].UpstreamRate);
}
Canvas.SetPos(Canvas.CurX-20,Canvas.CurY);
}
}
`endif
defaultproperties
{
bAllowBandwidthTesting=true
}

View File

@ -0,0 +1,798 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Class that implements a base cross-platform version of the auth interface
*/
Class OnlineAuthInterfaceImpl extends Object within OnlineSubsystemCommonImpl
implements(OnlineAuthInterface)
native;
/** The owning subsystem that this object is providing an implementation for */
var OnlineSubsystemCommonImpl OwningSubsystem;
/** Whether or not the auth interface is ready to perform authentication */
var const bool bAuthReady;
/** Auth session arrays; these use TSparseArray, which allow safe removal of array elements during iteration */
/** If we are a server, contains auth sessions for clients connected to the server */
var native const SparseArray_Mirror ClientAuthSessions{TSparseArray<FAuthSession>};
/** If we are a client, contains auth sessions for servers we are connected to */
var native const SparseArray_Mirror ServerAuthSessions{TSparseArray<FAuthSession>};
/** If we are a client, contains auth sessions for other clients we are playing with */
var native const SparseArray_Mirror PeerAuthSessions{TSparseArray<FAuthSession>};
/** If we are a client, contains auth sessions we created for a server */
var native const SparseArray_Mirror LocalClientAuthSessions{TSparseArray<FLocalAuthSession>};
/** If we are a server, contains auth sessions we created for clients */
var native const SparseArray_Mirror LocalServerAuthSessions{TSparseArray<FLocalAuthSession>};
/** If we are a client, contains auth sessions we created for other clients */
var native const SparseArray_Mirror LocalPeerAuthSessions{TSparseArray<FLocalAuthSession>};
/** Delegate/callback tracking arrays */
/** The list of 'OnAuthReady' delegates fired when the auth interface is ready to perform authentication */
var array<delegate<OnAuthReady> > AuthReadyDelegates;
/** The list of 'OnClientAuthRequest' delegates fired when the client receives an auth request from the server */
var array<delegate<OnClientAuthRequest> > ClientAuthRequestDelegates;
/** The list of 'OnServerAuthRequest' delegates fired when the server receives an auth request from a client */
var array<delegate<OnServerAuthRequest> > ServerAuthRequestDelegates;
/** The list of 'OnClientAuthResponse' delegates fired when the server receives auth data from a client */
var array<delegate<OnClientAuthResponse> > ClientAuthResponseDelegates;
/** The list of 'OnServerAuthResponse' delegates fired when the client receives auth data from the server */
var array<delegate<OnServerAuthResponse> > ServerAuthResponseDelegates;
/** The list of 'OnClientAuthComplete' delegates fired when the server receives the authentication result for a client */
var array<delegate<OnClientAuthComplete> > ClientAuthCompleteDelegates;
/** The list of 'OnServerAuthComplete' delegates fired when the client receives the authentication result for the server */
var array<delegate<OnServerAuthComplete> > ServerAuthCompleteDelegates;
/** The list of 'OnClientAuthEndSessionRequest' delegates fired when the client receives a request from the server, to end an active auth session */
var array<delegate<OnClientAuthEndSessionRequest> > ClientAuthEndSessionRequestDelegates;
/** The list of 'OnServerAuthRetryRequest' delegates fired when the server receives a request from the client, to retry server auth */
var array<delegate<OnServerAuthRetryRequest> > ServerAuthRetryRequestDelegates;
/** The list of 'OnClientConnectionClose' delegates fired when a client connection is closing on the server */
var array<delegate<OnClientConnectionClose> > ClientConnectionCloseDelegates;
/** The list of 'OnServerConnectionClose' delegates fired when a server connection is closing on the client */
var array<delegate<OnServerConnectionClose> > ServerConnectionCloseDelegates;
/**
* Used to check if the auth interface is ready to perform authentication
*
* @return whether or not the auth interface is ready
*/
function bool IsReady()
{
return bAuthReady;
}
/**
* Called when the auth interface is ready to perform authentication
*/
delegate OnAuthReady();
/**
* Sets the delegate used to notify when the auth interface is ready to perform authentication
*
* @param AuthReadyDelegate The delegate to use for notification
*/
function AddAuthReadyDelegate(delegate<OnAuthReady> AuthReadyDelegate)
{
if (AuthReadyDelegates.Find(AuthReadyDelegate) == INDEX_None)
{
AuthReadyDelegates[AuthReadyDelegates.Length] = AuthReadyDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param AuthReadyDelegate The delegate to remove from the list
*/
function ClearAuthReadyDelegate(delegate<OnAuthReady> AuthReadyDelegate)
{
local int i;
i = AuthReadyDelegates.Find(AuthReadyDelegate);
if (i != INDEX_None)
{
AuthReadyDelegates.Remove(i, 1);
}
}
/**
* Called when the client receives a message from the server, requesting a client auth session
*
* @param ServerUID The UID of the game server
* @param ServerIP The public (external) IP of the game server
* @param ServerPort The port of the game server
* @param bSecure whether or not the server has anticheat enabled (relevant to OnlineSubsystemSteamworks and VAC)
*/
//@HSL_BEGIN_XBOX
delegate OnClientAuthRequest(UniqueNetId ServerUID, IpAddr ServerIP, int ServerPort, bool bSecure);
//@HSL_END_XBOX
/**
* Sets the delegate used to notify when the client receives a message from the server, requesting a client auth session
*
* @param ClientAuthRequestDelegate The delegate to use for notifications
*/
function AddClientAuthRequestDelegate(delegate<OnClientAuthRequest> ClientAuthRequestDelegate)
{
if (ClientAuthRequestDelegates.Find(ClientAuthRequestDelegate) == INDEX_None)
{
ClientAuthRequestDelegates[ClientAuthRequestDelegates.Length] = ClientAuthRequestDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ClientAuthRequestDelegate The delegate to remove from the list
*/
function ClearClientAuthRequestDelegate(delegate<OnClientAuthRequest> ClientAuthRequestDelegate)
{
local int i;
i = ClientAuthRequestDelegates.Find(ClientAuthRequestDelegate);
if (i != INDEX_None)
{
ClientAuthRequestDelegates.Remove(i, 1);
}
}
/**
* Called when the server receives a message from a client, requesting a server auth session
*
* @param ClientConnection The NetConnection of the client the request came from
* @param ClientUID The UID of the client making the request
* @param ClientIP The IP of the client making the request
* @param ClientPort The port the client is on
*/
//@HSL_BEGIN_XBOX
delegate OnServerAuthRequest(Player ClientConnection, UniqueNetId ClientUID, IpAddr ClientIP, int ClientPort);
//@HSL_END_XBOX
/**
* Sets the delegate used to notify when the server receives a message from a client, requesting a server auth session
*
* @param ServerAuthRequestDelegate The delegate to use for notifications
*/
function AddServerAuthRequestDelegate(delegate<OnServerAuthRequest> ServerAuthRequestDelegate)
{
if (ServerAuthRequestDelegates.Find(ServerAuthRequestDelegate) == INDEX_None)
{
ServerAuthRequestDelegates[ServerAuthRequestDelegates.Length] = ServerAuthRequestDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ServerAuthRequestDelegate The delegate to remove from the list
*/
function ClearServerAuthRequestDelegate(delegate<OnServerAuthRequest> ServerAuthRequestDelegate)
{
local int i;
i = ServerAuthRequestDelegates.Find(ServerAuthRequestDelegate);
if (i != INDEX_None)
{
ServerAuthRequestDelegates.Remove(i, 1);
}
}
/**
* Called when the server receives auth data from a client, needed for authentication
*
* @param ClientUID The UID of the client
* @param ClientIP The IP of the client
* @param AuthTicketUID The UID used to reference the auth data
*/
//@HSL_BEGIN_XBOX
delegate OnClientAuthResponse(UniqueNetId ClientUID, IpAddr ClientIP, int AuthTicketUID);
//@HSL_END_XBOX
/**
* Sets the delegate used to notify when the server receives a auth data from a client
*
* @param ClientAuthResponseDelegate The delegate to use for notifications
*/
function AddClientAuthResponseDelegate(delegate<OnClientAuthResponse> ClientAuthResponseDelegate)
{
if (ClientAuthResponseDelegates.Find(ClientAuthResponseDelegate) == INDEX_None)
{
ClientAuthResponseDelegates[ClientAuthResponseDelegates.Length] = ClientAuthResponseDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ClientAuthResponseDelegate The delegate to remove from the list
*/
function ClearClientAuthResponseDelegate(delegate<OnClientAuthResponse> ClientAuthResponseDelegate)
{
local int i;
i = ClientAuthResponseDelegates.Find(ClientAuthResponseDelegate);
if (i != INDEX_None)
{
ClientAuthResponseDelegates.Remove(i, 1);
}
}
/**
* Called when the client receives auth data from the server, needed for authentication
*
* @param ServerUID The UID of the server
* @param ServerIP The IP of the server
* @param AuthTicketUID The UID used to reference the auth data
*/
//@HSL_BEGIN_XBOX
delegate OnServerAuthResponse(UniqueNetId ServerUID, IpAddr ServerIP, int AuthTicketUID);
//@HSL_END_XBOX
/**
* Sets the delegate used to notify when the client receives a auth data from the server
*
* @param ServerAuthResponseDelegate The delegate to use for notifications
*/
function AddServerAuthResponseDelegate(delegate<OnServerAuthResponse> ServerAuthResponseDelegate)
{
if (ServerAuthResponseDelegates.Find(ServerAuthResponseDelegate) == INDEX_None)
{
ServerAuthResponseDelegates[ServerAuthResponseDelegates.Length] = ServerAuthResponseDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ServerAuthResponseDelegate The delegate to remove from the list
*/
function ClearServerAuthResponseDelegate(delegate<OnServerAuthResponse> ServerAuthResponseDelegate)
{
local int i;
i = ServerAuthResponseDelegates.Find(ServerAuthResponseDelegate);
if (i != INDEX_None)
{
ServerAuthResponseDelegates.Remove(i, 1);
}
}
/**
* Called on the server, when the authentication result for a client auth session has returned
* NOTE: This is the first place, where a clients UID is verified as valid
*
* @param bSuccess whether or not authentication was successful
* @param ClientUID The UID of the client
* @param ClientConnection The connection associated with the client (for retrieving auth session data)
* @param ExtraInfo Extra information about authentication, e.g. failure reasons
*/
delegate OnClientAuthComplete(bool bSuccess, UniqueNetId ClientUID, Player ClientConnection, string ExtraInfo);
/**
* Sets the delegate used to notify when the server receives the authentication result for a client
*
* @param ClientAuthCompleteDelegate The delegate to use for notifications
*/
function AddClientAuthCompleteDelegate(delegate<OnClientAuthComplete> ClientAuthCompleteDelegate)
{
if (ClientAuthCompleteDelegates.Find(ClientAuthCompleteDelegate) == INDEX_None)
{
ClientAuthCompleteDelegates[ClientAuthCompleteDelegates.Length] = ClientAuthCompleteDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ClientAuthCompleteDelegate The delegate to remove from the list
*/
function ClearClientAuthCompleteDelegate(delegate<OnClientAuthComplete> ClientAuthCompleteDelegate)
{
local int i;
i = ClientAuthCompleteDelegates.Find(ClientAuthCompleteDelegate);
if (i != INDEX_None)
{
ClientAuthCompleteDelegates.Remove(i, 1);
}
}
/**
* Called on the client, when the authentication result for the server has returned
*
* @param bSuccess whether or not authentication was successful
* @param ServerUID The UID of the server
* @param ServerConnection The connection associated with the server (for retrieving auth session data)
* @param ExtraInfo Extra information about authentication, e.g. failure reasons
*/
delegate OnServerAuthComplete(bool bSuccess, UniqueNetId ServerUID, Player ServerConnection, string ExtraInfo);
/**
* Sets the delegate used to notify when the client receives the authentication result for the server
*
* @param ServerAuthCompleteDelegate The delegate to use for notifications
*/
function AddServerAuthCompleteDelegate(delegate<OnServerAuthComplete> ServerAuthCompleteDelegate)
{
if (ServerAuthCompleteDelegates.Find(ServerAuthCompleteDelegate) == INDEX_None)
{
ServerAuthCompleteDelegates[ServerAuthCompleteDelegates.Length] = ServerAuthCompleteDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ServerAuthCompleteDelegate The delegate to remove from the list
*/
function ClearServerAuthCompleteDelegate(delegate<OnServerAuthComplete> ServerAuthCompleteDelegate)
{
local int i;
i = ServerAuthCompleteDelegates.Find(ServerAuthCompleteDelegate);
if (i != INDEX_None)
{
ServerAuthCompleteDelegates.Remove(i, 1);
}
}
/**
* Called when the client receives a request from the server, to end an active auth session
*
* @param ServerConnection The server NetConnection
*/
delegate OnClientAuthEndSessionRequest(Player ServerConnection);
/**
* Sets the delegate used to notify when the client receives a request from the server, to end an active auth session
*
* @param ClientAuthEndSessionRequestDelegate The delegate to use for notifications
*/
function AddClientAuthEndSessionRequestDelegate(delegate<OnClientAuthEndSessionRequest> ClientAuthEndSessionRequestDelegate)
{
if (ClientAuthEndSessionRequestDelegates.Find(ClientAuthEndSessionRequestDelegate) == INDEX_None)
{
ClientAuthEndSessionRequestDelegates[ClientAuthEndSessionRequestDelegates.Length] = ClientAuthEndSessionRequestDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ClientAuthEndSessionRequestDelegate The delegate to remove from the list
*/
function ClearClientAuthEndSessionRequestDelegate(delegate<OnClientAuthEndSessionRequest> ClientAuthEndSessionRequestDelegate)
{
local int i;
i = ClientAuthEndSessionRequestDelegates.Find(ClientAuthEndSessionRequestDelegate);
if (i != INDEX_None)
{
ClientAuthEndSessionRequestDelegates.Remove(i, 1);
}
}
/**
* Called when the server receives a server auth retry request from a client
*
* @param ClientConnection The client NetConnection
*/
delegate OnServerAuthRetryRequest(Player ClientConnection);
/**
* Sets the delegate used to notify when the server receives a request from the client, to retry server auth
*
* @param ServerAuthRetryRequestDelegate The delegate to use for notifications
*/
function AddServerAuthRetryRequestDelegate(delegate<OnServerAuthRetryRequest> ServerAuthRetryRequestDelegate)
{
if (ServerAuthRetryRequestDelegates.Find(ServerAuthRetryRequestDelegate) == INDEX_None)
{
ServerAuthRetryRequestDelegates[ServerAuthRetryRequestDelegates.Length] = ServerAuthRetryRequestDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ServerAuthRetryRequestDelegate The delegate to remove from the list
*/
function ClearServerAuthRetryRequestDelegate(delegate<OnServerAuthRetryRequest> ServerAuthRetryRequestDelegate)
{
local int i;
i = ServerAuthRetryRequestDelegates.Find(ServerAuthRetryRequestDelegate);
if (i != INDEX_None)
{
ServerAuthRetryRequestDelegates.Remove(i, 1);
}
}
/**
* Called on the server when a clients net connection is closing (so auth sessions can be ended)
*
* @param ClientConnection The client NetConnection that is closing
*/
delegate OnClientConnectionClose(Player ClientConnection);
/**
* Sets the delegate used to notify when the a client net connection is closing
*
* @param ClientConnectionCloseDelegate The delegate to use for notifications
*/
function AddClientConnectionCloseDelegate(delegate<OnClientConnectionClose> ClientConnectionCloseDelegate)
{
if (ClientConnectionCloseDelegates.Find(ClientConnectionCloseDelegate) == INDEX_None)
{
ClientConnectionCloseDelegates[ClientConnectionCloseDelegates.Length] = ClientConnectionCloseDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ClientConnectionCloseDelegate The delegate to remove from the list
*/
function ClearClientConnectionCloseDelegate(delegate<OnClientConnectionClose> ClientConnectionCloseDelegate)
{
local int i;
i = ClientConnectionCloseDelegates.Find(ClientConnectionCloseDelegate);
if (i != INDEX_None)
{
ClientConnectionCloseDelegates.Remove(i, 1);
}
}
/**
* Called on the client when a server net connection is closing (so auth sessions can be ended)
*
* @param ServerConnection The server NetConnection that is closing
*/
delegate OnServerConnectionClose(Player ServerConnection);
/**
* Sets the delegate used to notify when the a server net connection is closing
*
* @param ServerConnectionCloseDelegate The delegate to use for notifications
*/
function AddServerConnectionCloseDelegate(delegate<OnServerConnectionClose> ServerConnectionCloseDelegate)
{
if (ServerConnectionCloseDelegates.Find(ServerConnectionCloseDelegate) == INDEX_None)
{
ServerConnectionCloseDelegates[ServerConnectionCloseDelegates.Length] = ServerConnectionCloseDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ServerConnectionCloseDelegate The delegate to remove from the list
*/
function ClearServerConnectionCloseDelegate(delegate<OnServerConnectionClose> ServerConnectionCloseDelegate)
{
local int i;
i = ServerConnectionCloseDelegates.Find(ServerConnectionCloseDelegate);
if (i != INDEX_None)
{
ServerConnectionCloseDelegates.Remove(i, 1);
}
}
/**
* Sends a client auth request to the specified client
* NOTE: It is important to specify the ClientUID from PreLogin
*
* @param ClientConnection The NetConnection of the client to send the request to
* @param ClientUID The UID of the client (as taken from PreLogin)
* @return whether or not the request kicked off successfully
*/
function bool SendClientAuthRequest(Player ClientConnection, UniqueNetId ClientUID);
/**
* Sends a server auth request to the server
*
* @param ServerUID The UID of the server
* @return whether or not the request kicked off successfully
*/
function bool SendServerAuthRequest(UniqueNetId ServerUID);
/**
* Sends the specified auth ticket from the client to the server
*
* @param AuthTicketUID The UID of the auth ticket, as retrieved by CreateClientAuthSession
* @return whether or not the auth ticket was sent successfully
*/
native function bool SendClientAuthResponse(int AuthTicketUID);
/**
* Sends the specified auth ticket from the server to the client
*
* @param ClientConnection The NetConnection of the client to send the auth ticket to
* @param AuthTicketUID The UID of the auth ticket, as retrieved by CreateServerAuthSession
* @return whether or not the auth ticket was sent successfully
*/
native function bool SendServerAuthResponse(Player ClientConnection, int AuthTicketUID);
/**
* Sends an auth kill request to the specified client
*
* @param ClientConnection The NetConnection of the client to send the request to
* @return whether or not the request was sent successfully
*/
native function bool SendClientAuthEndSessionRequest(Player ClientConnection);
/**
* Sends a server auth retry request to the server
*
* @return whether or not the request was sent successfully
*/
native function bool SendServerAuthRetryRequest();
/**
* Client auth functions, for authenticating clients with a game server
*/
/**
* Creates a client auth session with the server; the session doesn't start until the auth ticket is verified by the server
* NOTE: This must be called clientside
*
* @param ServerUID The UID of the server
* @param ServerIP The external/public IP address of the server
* @param ServerPort The port of the server
* @param bSecure whether or not the server has cheat protection enabled
* @param OutAuthTicketUID Outputs the UID of the auth data, which is used to verify the auth session on the server
* @return whether or not the local half of the auth session was kicked off successfully
*/
//@HSL_BEGIN_XBOX
function bool CreateClientAuthSession(UniqueNetId ServerUID, IpAddr ServerIP, int ServerPort, bool bSecure, out int OutAuthTicketUID);
/**
* Kicks off asynchronous verification and setup of a client auth session, on the server;
* auth success/failure is returned through OnClientAuthComplete
*
* @param ClientUID The UID of the client
* @param ClientIP The IP address of the client
* @param ClientPort The port the client is on
* @param AuthTicketUID The UID for the auth data sent by the client (as obtained through OnClientAuthResponse)
* @return whether or not asynchronous verification was kicked off successfully
*/
function bool VerifyClientAuthSession(UniqueNetId ClientUID, IpAddr ClientIP, int ClientPort, int AuthTicketUID);
/**
* Ends the clientside half of a client auth session
* NOTE: This call must be matched on the server, with EndRemoteClientAuthSession
*
* @param ServerUID The UID of the server
* @param ServerIP The external (public) IP address of the server
* @param ServerPort The port of the server
*/
native final function EndLocalClientAuthSession(UniqueNetId ServerUID, IpAddr ServerIP, int ServerPort);
/**
* Ends the serverside half of a client auth session
* NOTE: This call must be matched on the client, with EndLocalClientAuthSession
*
* @param ClientUID The UID of the client
* @param ClientIP The IP address of the client
*/
native final function EndRemoteClientAuthSession(UniqueNetId ClientUID, IpAddr ClientIP);
//@HSL_END_XBOX
/**
* Ends the clientside halves of all client auth sessions
* NOTE: This is the same as iterating AllLocalClientAuthSessions and ending each session with EndLocalClientAuthSession
*/
native function EndAllLocalClientAuthSessions();
/**
* Ends the serverside halves of all client auth sessions
* NOTE: This is the same as iterating AllClientAuthSessions and ending each session with EndRemoteClientAuthSession
*/
native function EndAllRemoteClientAuthSessions();
/**
* Server auth functions, for authenticating the server with clients
*/
/**
* Creates a server auth session with a client; the session doesn't start until the auth ticket is verified by the client
* NOTE: This must be called serverside; if using server auth, the server should create a server auth session for every client
*
* @param ClientUID The UID of the client
* @param ClientIP The IP address of the client
* @param ClientPort The port of the client
* @param OutAuthTicketUID Outputs the UID of the auth data, which is used to verify the auth session on the client
* @return whether or not the local half of the auth session was kicked off successfully
*/
//@HSL_BEGIN_XBOX
function bool CreateServerAuthSession(UniqueNetId ClientUID, IpAddr ClientIP, int ClientPort, out int OutAuthTicketUID);
/**
* Kicks off asynchronous verification and setup of a server auth session, on the client;
* auth success/failure is returned through OnServerAuthComplete
*
* @param ServerUID The UID of the server
* @param ServerIP The external/public IP address of the server
* @param AuthTicketUID The UID of the auth data sent by the server (as obtained through OnServerAuthResponse)
* @return whether or not asynchronous verification was kicked off successfully
*/
function bool VerifyServerAuthSession(UniqueNetId ServerUID, IpAddr ServerIP, int AuthTicketUID);
/**
* Ends the serverside half of a server auth session
* NOTE: This call must be matched on the other end, with EndRemoteServerAuthSession
*
* @param ClientUID The UID of the client
* @param ClientIP The IP address of the client
*/
native final function EndLocalServerAuthSession(UniqueNetId ClientUID, IpAddr ClientIP);
/**
* Ends the clientside half of a server auth session
* NOTE: This call must be matched on the other end, with EndLocalServerAuthSession
*
* @param ServerUID The UID of the server
* @param ServerIP The external/public IP address of the server
*/
native final function EndRemoteServerAuthSession(UniqueNetId ServerUID, IpAddr ServerIP);
//@HSL_END_XBOX
/**
* Ends the serverside halves of all server auth sessions
* NOTE: This is the same as iterating AllLocalServerAuthSessions and ending each session with EndLocalServerAuthSession
*/
native function EndAllLocalServerAuthSessions();
/**
* Ends the clientside halves of all server auth sessions
* NOTE: This is the same as iterating AllServerAuthSessions and ending each session with EndRemoteServerAuthSession
*/
native function EndAllRemoteServerAuthSessions();
/**
* Auth info access functions
*/
/**
* On a server, iterates all auth sessions for clients connected to the server
* NOTE: This iterator is remove-safe; ending a client auth session from within this iterator will not mess up the order of iteration
*
* @param OutSessionInfo Outputs the currently iterated auth session
*/
native function iterator AllClientAuthSessions(out AuthSession OutSessionInfo);
/**
* On a client, iterates all auth sessions we created for a server
* NOTE: This iterator is remove-safe; ending a local client auth session from within this iterator will not mess up the order of iteration
*
* @param OutSessionInfo Outputs the currently iterated auth session
*/
native function iterator AllLocalClientAuthSessions(out LocalAuthSession OutSessionInfo);
/**
* On a client, iterates all auth sessions for servers we are connecting/connected to
* NOTE: This iterator is remove-safe; ending a server auth session from within this iterator will not mess up the order of iteration
*
* @param OutSessionInfo Outputs the currently iterated auth session
*/
native function iterator AllServerAuthSessions(out AuthSession OutSessionInfo);
/**
* On a server, iterates all auth sessions we created for clients
* NOTE: This iterator is remove-safe; ending a local server auth session from within this iterator will not mess up the order of iteration
*
* @param OutSessionInfo Outputs the currently iterated auth session
*/
native function iterator AllLocalServerAuthSessions(out LocalAuthSession OutSessionInfo);
/**
* Finds the active/pending client auth session, for the client associated with the specified NetConnection
*
* @param ClientConnection The NetConnection associated with the client
* @param OutSessionInfo Outputs the auth session info for the client
* @return Returns TRUE if a session was found for the client, FALSE otherwise
*/
native function bool FindClientAuthSession(Player ClientConnection, out AuthSession OutSessionInfo);
/**
* Finds the clientside half of an active/pending client auth session
*
* @param ServerConnection The NetConnection associated with the server
* @param OutSessionInfo Outputs the auth session info for the client
* @return Returns TRUE if a session was found for the client, FALSE otherwise
*/
native function bool FindLocalClientAuthSession(Player ServerConnection, out LocalAuthSession OutSessionInfo);
/**
* Finds the active/pending server auth session, for the specified server connection
*
* @param ServerConnection The NetConnection associated with the server
* @param OutSessionInfo Outputs the auth session info for the server
* @return Returns TRUE if a session was found for the server, FALSE otherwise
*/
native function bool FindServerAuthSession(Player ServerConnection, out AuthSession OutSessionInfo);
/**
* Finds the serverside half of an active/pending server auth session
*
* @param ClientConnection The NetConnection associated with the client
* @param OutSessionInfo Outputs the auth session info for the server
* @return Returns TRUE if a session was found for the server, FALSE otherwise
*/
native function bool FindLocalServerAuthSession(Player ClientConnection, out LocalAuthSession OutSessionInfo);
/**
* Platform specific server information
*/
/**
* If this is a server, retrieves the platform-specific UID of the server; used for authentication (not supported on all platforms)
* NOTE: This is primarily used serverside, for listen host authentication
*
* @param OutServerUID The UID of the server
* @return whether or not the server UID was retrieved
*/
function bool GetServerUniqueId(out UniqueNetId OutServerUID);
/**
* If this is a server, retrieves the platform-specific IP and port of the server; used for authentication
* NOTE: This is primarily used serverside, for listen host authentication
*
* @param OutServerIP The public IP of the server (or, for platforms which don't support it, the local IP)
* @param OutServerPort The port of the server
*/
//@HSL_BEGIN_XBOX
function bool GetServerAddr(out IpAddr OutServerIP, out int OutServerPort);
//@HSL_END_XBOX

View File

@ -0,0 +1,219 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Provides an in game gameplay events/stats upload mechanism via the MCP backend
*/
class OnlineEventsInterfaceMcp extends MCPBase
native
implements(OnlineEventsInterface);
/**
* The types of events that are to be uploaded
* Keep in sync with [IpDrv.OnlineEventsInterfaceMcp] entries
*/
enum EEventUploadType
{
EUT_GenericStats,
EUT_ProfileData,
EUT_MatchmakingData,
EUT_PlaylistPopulation
};
/** Holds the configuration and instance data for event uploading */
struct native EventUploadConfig
{
/** The type of upload this config is for */
var const EEventUploadType UploadType;
/** The URL to send the data to */
var const string UploadUrl;
/** The amount of time to wait before erroring out */
var const float TimeOut;
/** Whether to compress the data before sending or not */
var const bool bUseCompression;
};
/**
* This is the array of upload task configurations
*/
var const config array<EventUploadConfig> EventUploadConfigs;
/** List of HTTP and compression objects that are POSTing the data */
var native const array<pointer> MCPEventPostObjects{struct FMCPEventPoster};
/** A list of upload types that are disabled (don't upload) */
var config array<EEventUploadType> DisabledUploadTypes;
/** if true, the stats data will be sent as a binary blob instead of XML */
var const config bool bBinaryStats;
cpptext
{
protected:
// FTickableObject interface
/**
* Ticks any outstanding async tasks that need processing
*
* @param DeltaTime the amount of time that has passed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
// Event upload specific methods
/**
* Finds the upload config for the type
*
* @param UploadType the type of upload that is being processed
*
* @return pointer to the config item or NULL if not found
*/
inline FEventUploadConfig* FindUploadConfig(BYTE UploadType)
{
// Make sure this config wasn't disabled
INT ItemIndex = DisabledUploadTypes.FindItemIndex(UploadType);
if (ItemIndex == INDEX_NONE)
{
for (INT EventIndex = 0; EventIndex < EventUploadConfigs.Num(); EventIndex++)
{
if (EventUploadConfigs(EventIndex).UploadType == UploadType)
{
return &EventUploadConfigs(EventIndex);
}
}
}
return NULL;
}
/**
* Common method for POST-ing a payload to an URL (determined by upload type)
*
* @param UploadType the type of upload that is happening
* @param Payload the data to send
* @param NetId unique id of the player sending the data
*
* @return TRUE if the send started successfully, FALSE otherwise
*/
virtual UBOOL UploadPayload(BYTE UploadType,const FString& Payload,const FUniqueNetId NetId);
/**
* Common method for POST-ing a payload to an URL (determined by upload type)
*
* @param UploadType the type of upload that is happening
* @param Payload the data to send
* @param NetId unique id of the player sending the data
*
* @return TRUE if the send started successfully, FALSE otherwise
*/
virtual UBOOL UploadBinaryPayload(BYTE UploadType,const TArray<BYTE>& Payload,const FUniqueNetId NetId);
/**
* Final method for POST-ing a payload to a URL. At this point it is assumed to be binary data
*
* @param bWasText will be true if the original post was text data
* @param UploadType the type of upload that is happening
* @param Payload the data to send
* @param NetId unique id of the player sending the data
*
* @return TRUE if the send started successfully, FALSE otherwise
*/
virtual UBOOL UploadFinalPayload(UBOOL bWasText,BYTE UploadType,const TArray<BYTE>& Payload,const FUniqueNetId NetId);
/**
* Converts the net id to a string
*
* @param Id the net id to convert
*
* @return the string form of the id
*/
virtual FString FormatAsString(const FUniqueNetId& Id)
{
return FString::Printf(TEXT("%I64u"),(QWORD&)Id);
}
/**
* Filters out escape characters that can't be sent to MCP via XML and
* replaces them with the XML allowed sequences
*
* @param Source the source string to modify
*
* @return a new string with the data escaped
*/
virtual FString EscapeString(const FString& Source);
/**
* Builds the URL of additional parameters used when posting playlist population data
*
* @param PlaylistId the playlist id being reported
* @param NumPlayers the number of players on the host
*
* @return the URL to use with all of the per platform extras
*/
virtual FString BuildPlaylistPopulationURLParameters(INT PlaylistId,INT NumPlayers);
/**
* Builds the URL of additional parameters used when posting data
*
* @param NetId the unique id of the player sending their data
*
* @return the URL to use with all of the per platform extras
*/
virtual FString BuildGenericURLParameters(const FUniqueNetId NetId);
/**
* Captures hardware information as a string for uploading to MCP
*/
virtual FString BuildHardwareXmlData(void)
{
return FString(TEXT("<Hardware />\r\n"));
}
/**
* @return platform specific XML data
*/
virtual FString BuildPlatformXmlData(void)
{
return TEXT("");
}
}
/**
* Sends the profile data to the server for statistics aggregation
*
* @param UniqueId the unique id for the player
* @param PlayerNick the player's nick name
* @param ProfileSettings the profile object that is being sent
* @param PlayerStorage the player storage object that is being sent
*
* @return true if the async task was started successfully, false otherwise
*/
native function bool UploadPlayerData(UniqueNetId UniqueId,string PlayerNick,OnlineProfileSettings ProfileSettings,OnlinePlayerStorage PlayerStorage);
/**
* Sends gameplay event data to MCP
*
* @param UniqueId the player that is sending the stats
* @param Payload the stats data to upload
*
* @return true if the async send started ok, false otherwise
*/
native function bool UploadGameplayEventsData(UniqueNetId UniqueId,const out array<byte> Payload);
/**
* Sends the network backend the playlist population for this host
*
* @param PlaylistId the playlist we are updating the population for
* @param NumPlayers the number of players on this host in this playlist
*
* @return true if the async send started ok, false otherwise
*/
native function bool UpdatePlaylistPopulation(int PlaylistId,int NumPlayers);
/**
* Sends matchmaking stats data to MCP
*
* @param UniqueId the unique id for the player
* @param MMStats object that contains aggregated matchmaking stats data
*
* @return true if the async send started ok, false otherwise
*/
native function bool UploadMatchmakingStats(UniqueNetId UniqueId,OnlineMatchmakingStats MMStats);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,290 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
*/
class OnlineImageDownloaderWeb extends Object
config(Engine);
`include(Engine\Classes\HttpStatusCodes.uci)
enum EOnlineImageDownloadState
{
/** Download has not been kicked off yet */
PIDS_NotStarted,
/** Currently waiting for download to finish */
PIDS_Downloading,
/** Downloaded successfully and Texture is ready to be used */
PIDS_Succeeded,
/** Download failed. Can't assume Texture contents are valid/updated */
PIDS_Failed
};
/** Entry for a cached FB profile image */
struct OnlineImageDownload
{
/** URL for the image */
var string URL;
/** HTTP request object used to download the image */
var HttpRequestInterface HTTPRequest;
/** Current download state */
var EOnlineImageDownloadState State;
/** Marked for deletion/reuse */
var bool bPendingRemoval;
/** Texture that will be updated with the downloaded image */
var Texture2DDynamic Texture;
};
/** Cache of textures images and the web requests used to download them */
var array<OnlineImageDownload> DownloadImages;
/** Maximum downloads that can be in flight at the same time */
var config int MaxSimultaneousDownloads;
/**
* Called whenever a download for an image has completed
*
* @param OnlineImageDownload cached entry that was downloaded
*/
delegate OnOnlineImageDownloaded(OnlineImageDownload CachedEntry);
/**
* Retrieve the texture for a given image URL if it has been successfully downloaded and is still cached
*
* @param URL original url of image request
*/
function Texture GetOnlineImageTexture(string URL)
{
local int FoundIdx;
FoundIdx = DownloadImages.Find('URL',URL);
if (FoundIdx != INDEX_NONE &&
DownloadImages[FoundIdx].State == PIDS_Succeeded)
{
return DownloadImages[FoundIdx].Texture;
}
return None;
}
/**
* Start the downloading/caching of the images for the given list of URLs
*
* @param URLs list of addresses to download
*/
function RequestOnlineImages(array<string> URLs)
{
local string URL;
local int FoundIdx,Idx;
// Start by marking any entries no longer needed for removal
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
// If existing User id entry not in new list of user ids then mark for removal
DownloadImages[Idx].bPendingRemoval = URLs.Find(DownloadImages[Idx].URL) == INDEX_NONE;
}
// Update/Add new user ids that are not being processed
foreach URLs(URL)
{
FoundIdx = DownloadImages.Find('URL',URL);
// If found existing entry then just treat as if downloaded
if (FoundIdx != INDEX_NONE)
{
OnOnlineImageDownloaded(DownloadImages[FoundIdx]);
}
// If no existing cached entry then need to update/add
else
{
// Find an entry marked for removal
FoundIdx = DownloadImages.Find('bPendingRemoval',true);
if (FoundIdx == INDEX_NONE)
{
// Add a new entry since no empty spots
FoundIdx = DownloadImages.Length;
DownloadImages.Length = DownloadImages.Length+1;
}
// Setup new cached image entry for user
DownloadImages[FoundIdx].URL = URL;
DownloadImages[FoundIdx].HTTPRequest = None;
DownloadImages[FoundIdx].State = PIDS_NotStarted;
DownloadImages[FoundIdx].bPendingRemoval = false;
if (DownloadImages[FoundIdx].Texture == None)
{
DownloadImages[FoundIdx].Texture = class'Texture2DDynamic'.static.Create(50,50);
}
}
}
// Remove the unused entries
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
if (DownloadImages[Idx].bPendingRemoval)
{
DownloadImages.Remove(Idx--,1);
}
}
// Try to start next download
DownloadNextImage();
}
/**
* @return total # of entries that are still being downloaded
*/
function int GetNumPendingDownloads()
{
local int Idx,Count;
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
if (DownloadImages[Idx].State == PIDS_Downloading)
{
Count++;
}
}
return Count;
}
/**
* Clear out the cached entries for the given user ids
*
* @param FBUserIds list of FB ids to clear
*/
function ClearDownloads(array<string> URLs)
{
local int Idx;
// Remove the entries matching the FB user ids
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
if (URLs.Find(DownloadImages[Idx].URL) != INDEX_NONE)
{
DownloadImages.Remove(Idx--,1);
}
}
}
/**
* Clear out all of the cached entries. Even if currently in flight
*/
function ClearAllDownloads()
{
DownloadImages.Length = 0;
}
/**
* Kick off the download for the next image. Up to MaxSimultaneousDownloads can be in flight at once
*/
private function DownloadNextImage()
{
local int Idx,PendingDownloads;
// Current pending downloads
PendingDownloads = GetNumPendingDownloads();
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
// Stop if we cant download any more
if (PendingDownloads >= MaxSimultaneousDownloads)
{
break;
}
// Find next available entry that needs to be processed
if (DownloadImages[Idx].State == PIDS_NotStarted)
{
//`log("FacebookImage:DownloadNextImage:2");
DownloadImages[Idx].HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (DownloadImages[Idx].HTTPRequest != None)
{
DownloadImages[Idx].HTTPRequest.SetVerb("GET");
DownloadImages[Idx].HTTPRequest.SetURL(DownloadImages[Idx].URL);
DownloadImages[Idx].HTTPRequest.SetProcessRequestCompleteDelegate(OnDownloadComplete);
if (DownloadImages[Idx].HTTPRequest.ProcessRequest())
{
DownloadImages[Idx].State = PIDS_Downloading;
PendingDownloads++;
}
}
}
}
}
/**
* Called when the download has completed for a single image
*
* @param OriginalRequest HTTP request object that was used to kick off the web request
* @param Response contains the response code, headers, and data from the GET
* @param bDidSucceed TRUE if the request completed
*/
private function OnDownloadComplete(HttpRequestInterface OriginalRequest, HttpResponseInterface Response, bool bDidSucceed)
{
local int FoundIdx;
local array<byte> JPEGData;
// Match up the request object in the list of cached entries
FoundIdx = DownloadImages.Find('HTTPRequest',OriginalRequest);
if (FoundIdx != INDEX_NONE)
{
// Check for valid/successful response, and that the contents are a JPEG file
if (bDidSucceed &&
Response != None &&
Response.GetResponseCode() == `HTTP_STATUS_OK &&
InStr(Response.GetHeader("Content-Type"),"jpeg",false,true) != INDEX_NONE)
{
// Mark successful completion
DownloadImages[FoundIdx].State = PIDS_Succeeded;
// Copy JPEG image data
Response.GetContent(JPEGData);
// Update the texture mip with the image data
DownloadImages[FoundIdx].Texture.UpdateMipFromJPEG(0,JPEGData);
}
else
{
// Failed to download
DownloadImages[FoundIdx].State = PIDS_Failed;
}
// Delegate called when download completed
OnOnlineImageDownloaded(DownloadImages[FoundIdx]);
// Done downloading so no longer need the request object
DownloadImages[FoundIdx].HTTPRequest = None;
}
// Try to start next download
DownloadNextImage();
}
/**
* Debug draw the images that have downloaded
*
* @param Canvas used to draw the profile image textures on screen
*/
function DebugDraw(Canvas Canvas)
{
local float PosX,PosY;
local int Idx;
PosX=0;
PosY=0;
for (Idx=0; Idx < DownloadImages.Length; Idx++)
{
if (DownloadImages[Idx].State == PIDS_Succeeded)
{
Canvas.SetDrawColor(255,255,255,255);
Canvas.SetPos(PosX,PosY);
Canvas.DrawTexture(DownloadImages[Idx].Texture,1);
Canvas.SetDrawColor(0,255,0,255);
Canvas.SetPos(PosX,PosY);
Canvas.DrawBox(DownloadImages[Idx].Texture.SizeX,DownloadImages[Idx].Texture.SizeY);
PosY += DownloadImages[Idx].Texture.SizeY;
}
else
{
Canvas.DrawBox(50,50);
PosY += 50;
}
if (PosY > Canvas.ClipY)
{
PosY = 0;
PosX += 50;
}
}
}
defaultproperties
{
}

View File

@ -0,0 +1,143 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Provides an in game news mechanism via the MCP backend
*/
class OnlineNewsInterfaceMcp extends MCPBase
native
implements(OnlineNewsInterface);
/** Holds the IP, hostname, URL, and news results for a particular news type */
struct native NewsCacheEntry
{
/** The URL to the news page that we're reading */
var const string NewsUrl;
/** The current async read state for the operation */
var EOnlineEnumerationReadState ReadState;
/** The type of news that we are reading */
var const EOnlineNewsType NewsType;
/** The results of the read */
var string NewsItem;
/** The amount of time before giving up on the read */
var const float TimeOut;
/** Whether the news item is in unicode or ansi */
var const bool bIsUnicode;
/** Pointer to the native helper object that performs the download */
var const native pointer HttpDownloader{class FHttpDownloadString};
};
/** The list of cached news items (ips, results, etc.) */
var config array<NewsCacheEntry> NewsItems;
/** The list of delegates to notify when the news read is complete */
var array<delegate<OnReadNewsCompleted> > ReadNewsDelegates;
/** Whether there are outstanding requests that need ticking or not */
var transient bool bNeedsTicking;
cpptext
{
// FTickableObject interface
/**
* Ticks any outstanding news read requests
*
* @param DeltaTime the amount of time that has passed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
// News specific methods
/**
* Finds the news cache entry for the specified type
*
* @param NewsType the type of news being read
*
* @return pointer to the news item or NULL if not found
*/
inline FNewsCacheEntry* FindNewsCacheEntry(BYTE NewsType)
{
for (INT NewsIndex = 0; NewsIndex < NewsItems.Num(); NewsIndex++)
{
if (NewsItems(NewsIndex).NewsType == NewsType)
{
return &NewsItems(NewsIndex);
}
}
return NULL;
}
}
/**
* Reads the game specific news from the online subsystem
*
* @param LocalUserNum the local user the news is being read for
* @param NewsType the type of news to read
*
* @return true if the async task was successfully started, false otherwise
*/
native function bool ReadNews(byte LocalUserNum,EOnlineNewsType NewsType);
/**
* Delegate used in notifying the UI/game that the news read operation completed
*
* @param bWasSuccessful true if the read completed ok, false otherwise
* @param NewsType the type of news read that just completed
*/
delegate OnReadNewsCompleted(bool bWasSuccessful,EOnlineNewsType NewsType);
/**
* Sets the delegate used to notify the gameplay code that news reading has completed
*
* @param ReadGameNewsDelegate the delegate to use for notifications
*/
function AddReadNewsCompletedDelegate(delegate<OnReadNewsCompleted> ReadNewsDelegate)
{
if (ReadNewsDelegates.Find(ReadNewsDelegate) == INDEX_NONE)
{
ReadNewsDelegates[ReadNewsDelegates.Length] = ReadNewsDelegate;
}
}
/**
* Removes the specified delegate from the notification list
*
* @param ReadGameNewsDelegate the delegate to use for notifications
*/
function ClearReadNewsCompletedDelegate(delegate<OnReadNewsCompleted> ReadGameNewsDelegate)
{
local int RemoveIndex;
RemoveIndex = ReadNewsDelegates.Find(ReadGameNewsDelegate);
if (RemoveIndex != INDEX_NONE)
{
ReadNewsDelegates.Remove(RemoveIndex,1);
}
}
/**
* Returns the game specific news item from the cache
*
* @param LocalUserNum the local user the news is being read for
* @param NewsType the type of news to read
*
* @return an empty string if no data was read, otherwise the contents of the news read
*/
function string GetNews(byte LocalUserNum,EOnlineNewsType NewsType)
{
local int NewsIndex;
// Search through the list of news items and return the one that matches
for (NewsIndex = 0; NewsIndex < NewsItems.Length; NewsIndex++)
{
if (NewsItems[NewsIndex].NewsType == NewsType)
{
return NewsItems[NewsIndex].NewsItem;
}
}
return "";
}
defaultproperties
{
}

View File

@ -0,0 +1,761 @@
/**
* 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"
}

View File

@ -0,0 +1,21 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*
* Per object config provider that exposes dynamic playlists to the UI system
*/
class OnlinePlaylistProvider extends UIResourceDataProvider
native(inherit)
PerObjectConfig
Config(Playlist);
/** Unique identifier for this Playlist */
var config int PlaylistId;
/** List of the names of the OnlinePlaylistGameTypeProvider for the game modes supported by this playlist */
var config array<Name> PlaylistGameTypeNames;
/** Localized display name for the playlist */
var config localized string DisplayName;
/** Value to determine sorting priority (highest is first in the list) */
var config int Priority;

View File

@ -0,0 +1,76 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Class that implements commonly needed members/features across all platforms
*/
class OnlineSubsystemCommonImpl extends OnlineSubsystem
native
abstract
config(Engine);
/**
* Holds the pointer to the platform specific FVoiceInterface implementation
* used for voice communication
*/
var const native transient pointer VoiceEngine{class FVoiceInterface};
/** Holds the maximum number of local talkers allowed */
var config int MaxLocalTalkers;
/** Holds the maximum number of remote talkers allowed (clamped to 30 which is XHV max) */
var config int MaxRemoteTalkers;
/** Whether speech recognition is enabled */
var config bool bIsUsingSpeechRecognition;
/** The object that handles the game interface implementation across platforms */
var OnlineGameInterfaceImpl GameInterfaceImpl;
/** The object that handles the auth interface implementation across platforms */
var OnlineAuthInterfaceImpl AuthInterfaceImpl;
/**
* Returns the name of the player for the specified index
*
* @param UserIndex the user to return the name of
*
* @return the name of the player at the specified index
*/
event string GetPlayerNicknameFromIndex(int UserIndex);
/**
* Determine if the player is registered in the specified session
*
* @param PlayerId the player to check if in session or not
* @return TRUE if the player is a registrant in the session
*/
native function bool IsPlayerInSession(name SessionName,UniqueNetId PlayerId);
/**
* Get a list of the net ids for the players currently registered on the session
*
* @param SessionName name of the session to find
* @param OutRegisteredPlayers [out] list of player net ids in the session (empty if not found)
*/
function GetRegisteredPlayers(name SessionName,out array<UniqueNetId> OutRegisteredPlayers)
{
local int Idx,PlayerIdx;
OutRegisteredPlayers.Length = 0;
for (Idx=0; Idx < Sessions.Length; Idx++)
{
// find session by name
if (Sessions[Idx].SessionName == SessionName)
{
// return list of player ids currently registered on the session
OutRegisteredPlayers.Length = Sessions[Idx].Registrants.Length;
for (PlayerIdx=0; PlayerIdx < Sessions[Idx].Registrants.Length; PlayerIdx++)
{
OutRegisteredPlayers[PlayerIdx] = Sessions[Idx].Registrants[PlayerIdx].PlayerNetId;
}
break;
}
}
}

View File

@ -0,0 +1,194 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Provides a mechanism for downloading arbitrary files from the MCP server
*/
class OnlineTitleFileDownloadBase extends MCPBase
native
implements(OnlineTitleFileInterface)
dependson(OnlineSubsystem);
/** Compression types supported */
enum EMcpFileCompressionType
{
MFCT_NONE,
MFCT_ZLIB
};
/** Holds the data used in downloading a file along with its web request */
struct native TitleFileWeb extends OnlineSubsystem.TitleFile
{
/** web response or string data if download succeeded */
var string StringData;
/** HTTP request that is in flight for the file request */
var HttpRequestInterface HTTPRequest;
/** The compression type of this File so the client knows how to de-compress the payload */
var EMcpFileCompressionType FileCompressionType;
};
/** The list of delegates to notify when a file is read */
var private array<delegate<OnReadTitleFileComplete> > ReadTitleFileCompleteDelegates;
/** The list of delegates to notify when a file list read is done */
var array<delegate<OnRequestTitleFileListComplete> > RequestTitleFileListCompleteDelegates;
/** The base URL to used for contacting for files, such that BaseUrl?TitleID=1234&FileName=MyFile.ini is the complete URL */
var config string BaseUrl;
/** Base URL for getting list of EMS files */
var config string RequestFileListURL;
/** Base URL for downloading a single EMS file */
var config string RequestFileURL;
/** The amount of time to allow for downloading of the file */
var config float TimeOut;
/** Allows the game to route a specific file or sets of files to a specific URL. If there is no special mapping for a file, then the base URL is used */
struct native FileNameToURLMapping
{
/** The name of the file to route to a specific URL */
var name FileName;
/** The URL to route the request to */
var name UrlMapping;
};
/** The routing table to look in when trying to find special URL handlers */
var config array<FileNameToURLMapping> FilesToUrls;
/**
* Delegate fired when a file read from the network platform's title specific storage is complete
*
* @param bWasSuccessful whether the file read was successful or not
* @param FileName the name of the file this was for
*/
delegate OnReadTitleFileComplete(bool bWasSuccessful,string FileName);
/**
* Starts an asynchronous read of the specified file from the network platform's
* title specific file store
*
* @param FileToRead the name of the file to read
*
* @return true if the calls starts successfully, false otherwise
*/
//@HSL_BEGIN_XBOX
function bool ReadTitleFile(string FileToRead, optional EOnlineFileType FileType = OFT_Binary);
//@HSL_END_XBOX
/**
* Adds the delegate to the list to be notified when a requested file has been read
*
* @param ReadTitleFileCompleteDelegate the delegate to add
*/
function AddReadTitleFileCompleteDelegate(delegate<OnReadTitleFileComplete> ReadTitleFileCompleteDelegate)
{
if (ReadTitleFileCompleteDelegates.Find(ReadTitleFileCompleteDelegate) == INDEX_NONE)
{
ReadTitleFileCompleteDelegates[ReadTitleFileCompleteDelegates.Length] = ReadTitleFileCompleteDelegate;
}
}
/**
* Removes the delegate from the notify list
*
* @param ReadTitleFileCompleteDelegate the delegate to remove
*/
function ClearReadTitleFileCompleteDelegate(delegate<OnReadTitleFileComplete> ReadTitleFileCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = ReadTitleFileCompleteDelegates.Find(ReadTitleFileCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
ReadTitleFileCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Copies the file data into the specified buffer for the specified file
*
* @param FileName the name of the file to read
* @param FileContents the out buffer to copy the data into
*
* @return true if the data was copied, false otherwise
*/
function bool GetTitleFileContents(string FileName,out array<byte> FileContents);
/**
* Determines the async state of the tile file read operation
*
* @param FileName the name of the file to check on
*
* @return the async state of the file read
*/
function EOnlineEnumerationReadState GetTitleFileState(string FileName);
/**
* Empties the set of downloaded files if possible (no async tasks outstanding)
*
* @return true if they could be deleted, false if they could not
*/
function bool ClearDownloadedFiles();
/**
* Empties the cached data for this file if it is not being downloaded currently
*
* @param FileName the name of the file to remove from the cache
*
* @return true if it could be deleted, false if it could not
*/
function bool ClearDownloadedFile(string FileName);
/**
* Async call to request a list of files (returned as string) from EMS
*/
//@HSL_BEGIN_XBOX
function bool RequestTitleFileList();
/**
* Delegate fired when the request for a list of files completes
*
* @param bWasSuccessful whether the request completed successfully
* @param ResultStr contains the list of files and associated meta data
*/
delegate OnRequestTitleFileListComplete(bool bWasSuccessful, array<string> FilePaths);
//@HSL_END_XBOX
/**
* Adds the delegate to the list to be notified when the list of requested files has been received
*
* @param RequestTitleFileListDelegate the delegate to add
*/
function AddRequestTitleFileListCompleteDelegate(delegate<OnRequestTitleFileListComplete> RequestTitleFileListDelegate)
{
if (RequestTitleFileListCompleteDelegates.Find(RequestTitleFileListDelegate) == INDEX_NONE)
{
RequestTitleFileListCompleteDelegates.AddItem(RequestTitleFileListDelegate);
}
}
/**
* Removes the delegate from the notify list
*
* @param RequestTitleFileListDelegate the delegate to remove
*/
function ClearRequestTitleFileListCompleteDelegate(delegate<OnRequestTitleFileListComplete> RequestTitleFileListDelegate)
{
local int RemoveIndex;
RemoveIndex = RequestTitleFileListCompleteDelegates.Find(RequestTitleFileListDelegate);
if (RemoveIndex != INDEX_NONE)
{
RequestTitleFileListCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Searches the filename to URL mapping table for the specified filename
*
* @param FileName the file to search the table for
*
* @param the URL to use to request the file or BaseURL if no special mapping is present
*/
native function string GetUrlForFile(string FileName);

View File

@ -0,0 +1,134 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Provides a mechanism for downloading arbitrary files from the MCP server
*/
class OnlineTitleFileDownloadMcp extends OnlineTitleFileDownloadBase
native;
/** Struct that matches one download object per file for parallel downloading */
struct native TitleFileMcp extends OnlineSubsystem.TitleFile
{
/** The class that will communicate with backend to download the file */
var private native const pointer HttpDownloader{class FHttpDownloadBinary};
};
/** The list of title files that have been read or are being read */
var private array<TitleFileMcp> TitleFiles;
/** The number of files in the array being processed */
var transient int DownloadCount;
/**
* Starts an asynchronous read of the specified file from the network platform's
* title specific file store
*
* @param FileToRead the name of the file to read
*
* @return true if the calls starts successfully, false otherwise
*/
native function bool ReadTitleFile(string FileToRead, optional EOnlineFileType FileType = OFT_Binary);
/**
* Copies the file data into the specified buffer for the specified file
*
* @param FileName the name of the file to read
* @param FileContents the out buffer to copy the data into
*
* @return true if the data was copied, false otherwise
*/
native function bool GetTitleFileContents(string FileName,out array<byte> FileContents);
/**
* Determines the async state of the tile file read operation
*
* @param FileName the name of the file to check on
*
* @return the async state of the file read
*/
function EOnlineEnumerationReadState GetTitleFileState(string FileName)
{
local int FileIndex;
FileIndex = TitleFiles.Find('FileName',FileName);
if (FileIndex != INDEX_NONE)
{
return TitleFiles[FileIndex].AsyncState;
}
return OERS_Failed;
}
/**
* Empties the set of downloaded files if possible (no async tasks outstanding)
*
* @return true if they could be deleted, false if they could not
*/
native function bool ClearDownloadedFiles();
/**
* Empties the cached data for this file if it is not being downloaded currently
*
* @param FileName the name of the file to remove from the cache
*
* @return true if it could be deleted, false if it could not
*/
native function bool ClearDownloadedFile(string FileName);
cpptext
{
// FTickableObject interface
/**
* Ticks any outstanding async tasks that need processing
*
* @param DeltaTime the amount of time that has passed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
// Helpers
/**
* Searches the list of files for the one that matches the filename
*
* @param FileName the file to search for
*
* @return the file details
*/
FORCEINLINE FTitleFileMcp* GetTitleFile(const FString& FileName)
{
// Search for the specified file
for (INT Index = 0; Index < TitleFiles.Num(); Index++)
{
FTitleFileMcp* TitleFile = &TitleFiles(Index);
if (TitleFile &&
TitleFile->Filename == FileName)
{
return TitleFile;
}
}
return NULL;
}
/**
* Fires the delegates so the caller knows the file download is complete
*
* @param TitleFile the information for the file that was downloaded
*/
void TriggerDelegates(const FTitleFile* TitleFile);
/**
* Builds the URL to use when fetching the specified file
*
* @param FileName the file that is being requested
*
* @return the URL to use with all of the per platform extras
*/
virtual FString BuildURLParameters(const FString& FileName)
{
return FString::Printf(TEXT("TitleID=%d&PlatformID=%d&Filename=%s"),
appGetTitleId(),
(DWORD)appGetPlatformType(),
*FileName);
}
}

View File

@ -0,0 +1,337 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Title file downloading implementation via web service request
*/
class OnlineTitleFileDownloadWeb extends OnlineTitleFileDownloadBase
native;
`include(Engine\Classes\HttpStatusCodes.uci);
/** The list of title files that have been read or are being read */
var private array<TitleFileWeb> TitleFiles;
/**
* Uncompress the title file
*
* @param FileCompressionType enum that that informs how to uncompress the file
* @param CompressedFileContents Source data to uncompress
* @param UncompressedFileContents the out buffer to copy the data into
*/
native function bool UncompressTitleFileContents(EMcpFileCompressionType FileCompressionType, const out array<byte> CompressedFileContents, out array<byte> UncompressedFileContents);
/**
* Starts an asynchronous read of the specified file from the network platform's
* title specific file store
*
* @param FileToRead the name of the file to read
*
* @return true if the calls starts successfully, false otherwise
*/
function bool ReadTitleFile(string FileToRead, optional EOnlineFileType FileType = OFT_Binary)
{
local int FileIndex,Idx;
local string URL;
// check for a prior request
FileIndex = INDEX_NONE;
for (Idx=0; Idx < TitleFiles.Length; Idx++)
{
// case sensitive
if (InStr(TitleFiles[Idx].Filename,FileToRead,true,false) != INDEX_NONE)
{
FileIndex = Idx;
break;
}
}
// add new entry for this file request if not found
if (FileIndex == INDEX_NONE)
{
FileIndex = TitleFiles.Length;
TitleFiles.Length = TitleFiles.Length + 1;
TitleFiles[FileIndex].Filename = FileToRead;
TitleFiles[FileIndex].AsyncState = OERS_NotStarted;
}
// file has been downloaded before successfully so already done
if (TitleFiles[FileIndex].AsyncState == OERS_Done)
{
TriggerDelegates(true,FileToRead);
}
// file has been downloaded before but failed
else if (TitleFiles[FileIndex].AsyncState == OERS_Failed)
{
TriggerDelegates(false,FileToRead);
return false;
}
// download needs to start if not already in progress
else if (TitleFiles[FileIndex].AsyncState != OERS_InProgress)
{
// mark the file entry as pending download
TitleFiles[FileIndex].AsyncState = OERS_InProgress;
// tack on the filename to the base/overridden URL
URL = GetUrlForFile(FileToRead) $ FileToRead;
`Log(`location @ "starting read for title file"
@"url="$URL);
// send off web request and register for delegate for its completion
TitleFiles[FileIndex].HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (TitleFiles[FileIndex].HTTPRequest != None)
{
TitleFiles[FileIndex].HTTPRequest.OnProcessRequestComplete = OnFileDownloadComplete;
TitleFiles[FileIndex].HTTPRequest.SetVerb("GET");
TitleFiles[FileIndex].HTTPRequest.SetURL(URL);
TitleFiles[FileIndex].HTTPRequest.ProcessRequest();
}
}
return true;
}
/**
* Delegate called on each completed web request
*
* @param OriginalRequest - The original request object that spawned the response
* @param HttpResponse - The response object. Could be None if the request failed spectacularly. If the request failed to receive a complete
* response for some reason, this could contain a valid Response object with as much info as could be retrieved.
* Always use the bDidSucceed parameter to determine if the entire response was received successfully.
* @param bSucceeded - whether the response succeeded. If it did not, you should not trust the payload or headers.
* Basically indicates a net failure occurred while receiving the response.
*/
private function OnFileDownloadComplete(HttpRequestInterface Request, HttpResponseInterface Response, bool bDidSucceed)
{
local bool bSuccess;
local int FileIndex,Idx;
local string Filename;
local array<byte> BinaryData;
local string FileCompressionTypeString;
if (bDidSucceed)
{
FileIndex = INDEX_NONE;
// find file entry for this request based on what was passed on the URL
for (Idx=0; Idx < TitleFiles.Length; Idx++)
{
if (TitleFiles[Idx].HTTPRequest == Request)
{
FileIndex = Idx;
break;
}
}
`Log(`location @ ""
@"FileIndex="$FileIndex
@"OriginalURL="$Request.GetURL()
@"ResponseCode="$Response.GetResponseCode()
@"ContentLength="$Response.GetContentLength());
if (FileIndex != INDEX_NONE)
{
Filename = TitleFiles[FileIndex].Filename;
// remove ref to request since it is now complete
TitleFiles[FileIndex].HTTPRequest = None;
TitleFiles[FileIndex].AsyncState = OERS_Failed;
// only successful response code as we're not handling any redirects
if (Response.GetResponseCode() == `HTTP_STATUS_OK)
{
bSuccess = true;
// copy the payload data from the web request
Response.GetContent(BinaryData);
TitleFiles[FileIndex].Data = BinaryData;
// Get the Compression Type from the Response Header
FileCompressionTypeString = Response.GetHeader("Mcp-Content-Encoding");
// Convert CompressionTypeString stored in the datastore to the enum used on the client
switch(FileCompressionTypeString)
{
case "MFCT_ZLIB":
TitleFiles[FileIndex].FileCompressionType = MFCT_ZLIB;
break;
default:
TitleFiles[FileIndex].FileCompressionType = MFCT_NONE;
}
// mark as successfully done
TitleFiles[FileIndex].AsyncState = OERS_Done;
}
else
{
// clear failed data
TitleFiles[FileIndex].AsyncState = OERS_Failed;
TitleFiles[FileIndex].Data.Length = 0;
}
}
else
{
`Log(`location @ "No entry found for"
@"FileIndex="$FileIndex);
}
}
else
{
`Log(`location @ "web request for file download failed");
}
// web request is complete and delegate is triggered for success/failure
TriggerDelegates(bSuccess,Filename);
}
/**
* Runs the delegates registered on this interface for each file download request
*
* @param bSuccess true if the request was successful
* @param FileRead name of the file that was read
*/
private native function TriggerDelegates(bool bSuccess,string FileRead);
/**
* Copies the file data into the specified buffer for the specified file
*
* @param FileName the name of the file to read
* @param FileContents the out buffer to copy the data into
*
* @return true if the data was copied, false otherwise
*/
native function bool GetTitleFileContents(string FileName,out array<byte> FileContents);
/**
* Determines the async state of the tile file read operation
*
* @param FileName the name of the file to check on
*
* @return the async state of the file read
*/
function EOnlineEnumerationReadState GetTitleFileState(string FileName)
{
local int FileIndex;
FileIndex = TitleFiles.Find('FileName',FileName);
if (FileIndex != INDEX_NONE)
{
return TitleFiles[FileIndex].AsyncState;
}
return OERS_Failed;
}
/**
* Empties the set of downloaded files if possible (no async tasks outstanding)
*
* @return true if they could be deleted, false if they could not
*/
native function bool ClearDownloadedFiles();
/**
* Empties the cached data for this file if it is not being downloaded currently
*
* @param FileName the name of the file to remove from the cache
*
* @return true if it could be deleted, false if it could not
*/
native function bool ClearDownloadedFile(string FileName);
/**
* Async call to request a list of files (returned as string) from EMS
*/
function bool RequestTitleFileList()
{
local HttpRequestInterface HTTPRequest;
local string URL;
HTTPRequest = class'HttpFactory'.static.CreateRequest();
if (HTTPRequest != None)
{
URL = GetBaseURL() $ RequestFileListURL $ GetAppAccessURL();
HTTPRequest.OnProcessRequestComplete = OnFileListReceived;
HTTPRequest.SetVerb("GET");
HTTPRequest.SetURL(URL);
HTTPRequest.ProcessRequest();
}
else
{
`log(`location@"HTTPRequest object missing");
}
return true;
}
/**
* Delegate for when the EMS file list is received
*
* @param OriginalRequest - The original request object that spawned the response
* @param HttpResponse - The response object. Could be None if the request failed spectacularly. If the request failed to receive a complete
* response for some reason, this could contain a valid Response object with as much info as could be retrieved.
* Always use the bDidSucceed parameter to determine if the entire response was received successfully.
* @param bSucceeded - whether the response succeeded. If it did not, you should not trust the payload or headers.
* Basically indicates a net failure occurred while receiving the response.
*/
function OnFileListReceived(HttpRequestInterface Request, HttpResponseInterface Response, bool bDidSucceed)
{
local int Index;
local delegate<OnRequestTitleFileListComplete> RequestTitleFileListDelegate;
local array<string> ResponseStr;
local bool bSuccess;
ResponseStr.length = 0;
if (bDidSucceed)
{
if (Response != None &&
Response.GetResponseCode() == `HTTP_STATUS_OK)
{
ResponseStr.AddItem(Response.GetContentAsString());
bSuccess = true;
}
else
{
`log(`location@"Download of file list failed. Bad response."
@"ResponseCode="$Response.GetResponseCode()
@"URL="$Request.GetURL());
}
}
else
{
`log(`location@"Download of file list failed.");
}
// Call the completion delegate for receiving the file list
for (Index=0; Index < RequestTitleFileListCompleteDelegates.Length; Index++)
{
RequestTitleFileListDelegate = RequestTitleFileListCompleteDelegates[Index];
if (RequestTitleFileListDelegate != None)
{
RequestTitleFileListDelegate(bSuccess,ResponseStr);
}
}
}
/**
* Build the clashmob specific Url for downloading a given file
*
* @param FileName the file to search the table for
*
* @param the URL to use to request the file or BaseURL if no special mapping is present
*/
function string GetUrlForFile(string FileName)
{
local string Url;
Url = GetBaseURL() $ RequestFileURL $ GetAppAccessURL() $
"&dlName=";
return Url;
}
cpptext
{
/**
* Ticks any outstanding async tasks that need processing
*
* @param DeltaTime the amount of time that has passed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Searches the list of files for the one that matches the filename
*
* @param FileName the file to search for
*
* @return the file details
*/
FTitleFileWeb* GetTitleFile(const FString& FileName);
}

View File

@ -0,0 +1,214 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is the base class for the client/host beacon classes.
*/
class PartyBeacon extends Object
native
inherits(FTickableObject)
config(Engine);
/** The port that the party beacon will listen on */
var config int PartyBeaconPort;
/** The object that is used to send/receive data with the remote host/client */
var native transient pointer Socket{FSocket};
/** The types of packets being sent */
enum EReservationPacketType
{
RPT_UnknownPacketType,
// Sent by a client that wants to reserve space
RPT_ClientReservationRequest,
// Sent by a client that wants to update an existing party reservation
RPT_ClientReservationUpdateRequest,
// Sent by a client that is backing out of its request
RPT_ClientCancellationRequest,
// Sent by the host telling the client whether the reservation was accepted or not
RPT_HostReservationResponse,
// Sent by the host telling all clients the current reservation count
RPT_HostReservationCountUpdate,
// Sent by the host telling all clients to travel to a specified destination
RPT_HostTravelRequest,
// When doing full party matching, tells the clients the host is ready
RPT_HostIsReady,
// Tells the clients that the host has canceled this matching session
RPT_HostHasCancelled,
// Sent periodically to tell the host/client that the other end is there
RPT_Heartbeat
};
/** The result code that will be returned during party reservation */
enum EPartyReservationResult
{
// An unknown error happened
PRR_GeneralError,
// All available reservations are booked
PRR_PartyLimitReached,
// Wrong number of players to join the session
PRR_IncorrectPlayerCount,
// No response from the host
PRR_RequestTimedOut,
// Already have a reservation entry for the requesting party leader
PRR_ReservationDuplicate,
// Couldn't find the party leader specified for a reservation update request
PRR_ReservationNotFound,
// Space was available and it's time to join
PRR_ReservationAccepted,
// The beacon is paused and not accepting new connections
PRR_ReservationDenied
};
/** A player that has/is requesting a reservation */
struct native PlayerReservation
{
/** The unique identifier for this player */
var UniqueNetId NetId;
/** The skill of the player */
var int Skill;
/** The player experience level */
var int XpLevel;
/** The raw skill value */
var double Mu;
/** The uncertainty of that raw skill value */
var double Sigma;
/** Seconds since we checked to see if the player reservation exists in the session */
var float ElapsedSessionTime;
structcpptext
{
/** Constructors */
FPlayerReservation() {}
FPlayerReservation(EEventParm)
{
appMemzero(this, sizeof(FPlayerReservation));
}
/**
* Serialize from NBO buffer to FPlayerReservation
*/
friend FNboSerializeFromBuffer& operator>>(FNboSerializeFromBuffer& Ar,FPlayerReservation& PlayerRes);
/**
* Serialize from FPlayerReservation to NBO buffer
*/
friend FNboSerializeToBuffer& operator<<(FNboSerializeToBuffer& Ar,const FPlayerReservation& PlayerRes);
}
};
/** Holds information about a party that has reserved space in the session */
struct native PartyReservation
{
/** The team this party was assigned to */
var int TeamNum;
/** The party leader that has requested a reservation */
var UniqueNetId PartyLeader;
/** The list of members of the party (includes the party leader) */
var array<PlayerReservation> PartyMembers;
};
/** Used to determine whether to use deferred destruction or not */
var bool bIsInTick;
/** True if the beacon should be destroyed at the end of the tick */
var bool bWantsDeferredDestroy;
/** The maximum amount of time to pass between heartbeat packets being sent */
var config float HeartbeatTimeout;
/** The elapsed time that has passed since the last heartbeat */
var float ElapsedHeartbeatTime;
/** Whether to the socket(s) or not (not during travel) */
var bool bShouldTick;
/** The name to use when logging (helps debugging) */
var name BeaconName;
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;
}
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Converts a host response code to a readable string
*
* @param Result the code to translate
*
* @return the string that maps to it
*/
inline const TCHAR* PartyReservationResultToString(EPartyReservationResult Result)
{
switch (Result)
{
case PRR_PartyLimitReached: return TEXT("PRR_PartyLimitReached");
case PRR_IncorrectPlayerCount: return TEXT("PRR_IncorrectPlayerCount");
case PRR_RequestTimedOut: return TEXT("PRR_RequestTimedOut");
case PRR_ReservationDuplicate: return TEXT("PRR_ReservationDuplicate");
case PRR_ReservationNotFound: return TEXT("PRR_ReservationNotFound");
case PRR_ReservationAccepted: return TEXT("PRR_ReservationAccepted");
case PRR_ReservationDenied: return TEXT("PRR_ReservationDenied");
}
return TEXT("PRR_GeneralError");
}
/**
* Sends a heartbeat packet to the specified socket
*
* @param Socket the socket to send the data on
*
* @return TRUE if it sent ok, FALSE if there was an error
*/
UBOOL SendHeartbeat(FSocket* Socket);
/**
* @return the max value for the packet types handled by this beacon
*/
virtual BYTE GetMaxPacketValue()
{
return RPT_MAX;
}
}
/**
* Stops listening for requests/responses and releases any allocated memory
*/
native event DestroyBeacon();
/**
* Called when the beacon has completed destroying its socket
*/
delegate OnDestroyComplete();
defaultproperties
{
bShouldTick=true
}

View File

@ -0,0 +1,247 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is used to connect to a network beacon in order to request
* a reservation for their party with that host
*/
class PartyBeaconClient extends PartyBeacon
native;
/** Holds a reference to the data that is used to reach the potential host */
var const OnlineGameSearchResult HostPendingRequest;
/** The request to send once the socket is established */
var PartyReservation PendingRequest;
/** Used to drive the client state machine */
enum EPartyBeaconClientState
{
// Inactive or unknown
PBCS_None,
// A connection request is outstanding with host
PBCS_Connecting,
// Connected to the host and is ready to send
PBCS_Connected,
// Failed to establish a connection
PBCS_ConnectionFailed,
// Client has sent to the host and is awaiting for replies
PBCS_AwaitingResponse,
// The client has closed the connection
PBCS_Closed
};
/** The state of the client beacon */
var EPartyBeaconClientState ClientBeaconState;
/** Determine the pending request to be sent to the host */
enum EPartyBeaconClientRequest
{
// Pending client request for creating a new reservation on the host
PBClientRequest_NewReservation,
// Pending client request for updating an existing reservation on the host
PBClientRequest_UpdateReservation
};
/** The pending request to be sent */
var EPartyBeaconClientRequest ClientBeaconRequestType;
/** Indicates how long the client should wait for a response before timing out and trying a new server */
var config float ReservationRequestTimeout;
/** Used to track how long we've been waiting for a response */
var float ReservationRequestElapsedTime;
/** Name of the class to use for address resolving and registering */
var config string ResolverClassName;
/** Class to use for address resolving and registering */
var class<ClientBeaconAddressResolver> ResolverClass;
/** Platform specific address resolver for this beacon. Instantiated using the ResolverClass type. */
var ClientBeaconAddressResolver Resolver;
cpptext
{
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Loads the class specified for the Resolver and constructs it if needed
*/
void InitResolver(void);
/**
* Creates a beacon that will send requests to remote hosts
*
* @param Addr the address that we are connecting to (needs to be resolved)
*
* @return true if the beacon was created successfully, false otherwise
*/
UBOOL InitClientBeacon(const FInternetIpAddr& Addr);
/**
* Once the socket has been established, it sends the pending request to the host
*/
virtual void SendReservationRequest(void);
/**
* Processes a packet that was received from the host indicating success or
* failure for our reservation
*
* @param Packet the packet that the host sent
* @param PacketSize the size of the packet to process
*/
void ProcessHostResponse(BYTE* Packet,INT PacketSize);
/**
* Routes the response packet received from a host to the correct handler based on its type.
* Overridden by base implementations to handle custom packet types
*
* @param HostResponsePacketType packet ID from EReservationPacketType (or derived version) that represents a host response to this client
* @param FromBuffer the packet serializer to read from
* @return TRUE if the data packet type was processed
*/
virtual UBOOL HandleHostResponsePacketType(BYTE HostResponsePacketType,FNboSerializeFromBuffer& FromBuffer);
/**
* Processes a reservation response packet that was received from the host
*
* @param FromBuffer the packet serializer to read from
*/
virtual void ProcessReservationResponse(FNboSerializeFromBuffer& FromBuffer);
/**
* Processes a reservation count update packet that was received from the host
*
* @param FromBuffer the packet serializer to read from
*/
virtual void ProcessReservationCountUpdate(FNboSerializeFromBuffer& FromBuffer);
/**
* Processes a heartbeat update, sends a heartbeat back, and clears the timer
*/
void ProcessHeartbeat(void);
/**
* Notifies the delegates that the host is ready to play
*/
void ProcessHostIsReady(void);
/**
* Processes a travel request packet that was received from the host
*
* @param FromBuffer the packet serializer to read from
*/
void ProcessTravelRequest(FNboSerializeFromBuffer& FromBuffer);
/** Unregisters the address and zeros the members involved to prevent multiple releases */
void CleanupAddress(void);
/**
* Handles checking for the transition from connecting to connected (socket established)
*/
void CheckConnectionStatus(void);
/**
* Checks the socket for a response from the and processes if present
*/
void ReadResponse(void);
/** Common routine for canceling matchmaking */
inline void ProcessHostCancelled(void)
{
CleanupAddress();
delegateOnHostHasCancelled();
}
/** Common routine for notifying of a timeout trying to talk to host */
inline void ProcessHostTimeout(void)
{
CleanupAddress();
delegateOnReservationRequestComplete(PRR_RequestTimedOut);
}
}
/**
* Called by the beacon when a reservation request has been responded to by the destination host
*
* @param ReservationResult whether there was space allocated for the party or not
*/
delegate OnReservationRequestComplete(EPartyReservationResult ReservationResult);
/**
* Called by the beacon when the host sends a reservation count update packet so
* that any UI can be updated
*
* @param ReservationRemaining the number of reservations that are still available
*/
delegate OnReservationCountUpdated(int ReservationRemaining);
/**
* Called by the beacon when the host sends a request for all clients to travel to
* the destination included in the packet
*
* @param SessionName the name of the session to register
* @param SearchClass the search that should be populated with the session
* @param PlatformSpecificInfo the binary data to place in the platform specific areas
*/
delegate OnTravelRequestReceived(name SessionName,class<OnlineGameSearch> SearchClass,byte PlatformSpecificInfo[80]);
/**
* Called by the beacon when the host sends the "ready" packet, so the client
* can connect to the host to start the match
*/
delegate OnHostIsReady();
/**
* Called by the beacon when the host sends the "cancellation" packet, so the client
* can return to finding a new host
*/
delegate OnHostHasCancelled();
/**
* Sends a request to the remote host to allow the specified members to reserve space
* in the host's session. Note this request is async and the results will be sent via
* the delegate
*
* @param DesiredHost the server that the connection will be made to
* @param RequestingPartyLeader the leader of this party that will be joining
* @param Players the list of players that want to reserve space
*
* @return true if the request able to be sent, false if it failed to send
*/
native function bool RequestReservation(const out OnlineGameSearchResult DesiredHost,UniqueNetId RequestingPartyLeader,const out array<PlayerReservation> Players);
/**
* Sends a request to the remote host to update an existing reservation for the
* specified party leader. Any new players not already in the party leader's reservation
* will be added to that reservation on the host. Host sends a EPartyReservationResult back.
*
* @param DesiredHost the server that the connection will be made to
* @param RequestingPartyLeader party leader that will be updating his existing reservation
* @param PlayersToAdd the list of players that want to reserve space in an existing reservation
*
* @return true if the request able to be sent, false if it failed to send
*/
native function bool RequestReservationUpdate(const out OnlineGameSearchResult DesiredHost,UniqueNetId RequestingPartyLeader,const out array<PlayerReservation> PlayersToAdd);
/**
* Sends a cancellation message to the remote host so that it knows there is more
* space available
*
* @param CancellingPartyLeader the leader of this party that wants to cancel
*
* @return true if the request able to be sent, false if it failed to send
*/
native function bool CancelReservation(UniqueNetId CancellingPartyLeader);
/**
* Stops listening for requests/responses and releases any allocated memory
*/
native event DestroyBeacon();

View File

@ -0,0 +1,515 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* This class is used to create a network accessible beacon for responding
* to reservation requests for party matches. It handles all tracking of
* who has reserved space, how much space is available, and how many parties
* can reserve space.
*/
class PartyBeaconHost extends PartyBeacon
native;
/** Holds the information for a client and whether they've timed out */
struct native ClientBeaconConnection
{
/** The unique id of the party leader for this connection */
var UniqueNetId PartyLeader;
/** How long it's been since the last heartbeat */
var float ElapsedHeartbeatTime;
/** The socket this client is communicating on */
var native transient pointer Socket{FSocket};
};
/** The object that is used to send/receive data with the remote host/client */
var const array<ClientBeaconConnection> Clients;
/** The number of teams that these players will be divided amongst */
var const int NumTeams;
/** The number of players required for a full team */
var const int NumPlayersPerTeam;
/** The number of players that are allowed to reserve space */
var const int NumReservations;
/** The number of slots that have been consumed by parties in total (saves having to iterate and sum) */
var const int NumConsumedReservations;
/** The list of accepted reservations */
var const array<PartyReservation> Reservations;
/** The online session name that players will register with */
var name OnlineSessionName;
/** The number of connections to allow before refusing them */
var config int ConnectionBacklog;
/** Team to force all reservations to in single team situations */
var const int ForceTeamNum;
/** The team the host (owner of the beacon) is assigned to when random teams are chosen */
var const int ReservedHostTeamNum;
/** Force new reservations to teams with the smallest accommodating need size, otherwise team assignment is random */
var bool bBestFitTeamAssignment;
enum EPartyBeaconHostState
{
/** New reservations are accepted if possible */
PBHS_AllowReservations,
/** New reservations are denied */
PBHS_DenyReservations
};
/** Current state of the beacon wrt allowing reservation requests */
var const EPartyBeaconHostState BeaconState;
cpptext
{
/**
* Ticks the network layer to see if there are any requests or responses to requests
*
* @param DeltaTime the amount of time that has elapsed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/** Accepts any pending connections and adds them to our queue */
void AcceptConnections(void);
/**
* Reads the socket and processes any data from it
*
* @param ClientConn the client connection that sent the packet
*
* @return TRUE if the socket is ok, FALSE if it is in error
*/
UBOOL ReadClientData(FClientBeaconConnection& ClientConn);
/**
* Processes a packet that was received from a client
*
* @param Packet the packet that the client sent
* @param PacketSize the size of the packet to process
* @param ClientConn the client connection that sent the packet
*/
void ProcessRequest(BYTE* Packet,INT PacketSize,FClientBeaconConnection& ClientConn);
/**
* Routes the packet received from a client to the correct handler based on its type.
* Overridden by base implementations to handle custom data packet types
*
* @param RequestPacketType packet ID from EReservationPacketType (or derived version) that represents a client request
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
* @return TRUE if the requested packet type was processed
*/
virtual UBOOL HandleClientRequestPacketType(BYTE RequestPacketType,FNboSerializeFromBuffer& FromBuffer,FClientBeaconConnection& ClientConn);
/**
* Processes a reservation packet that was received from a client
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
virtual void ProcessReservationRequest(FNboSerializeFromBuffer& FromBuffer,FClientBeaconConnection& ClientConn);
/**
* Processes a reservation update packet that was received from a client
* The update finds an existing reservation based on the party leader and
* then tries to add new players to it (can't remove). Then sends a response to
* the client based on the update results (see EPartyReservationResult).
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
virtual void ProcessReservationUpdateRequest(FNboSerializeFromBuffer& FromBuffer,FClientBeaconConnection& ClientConn);
/**
* Processes a cancellation packet that was received from a client
*
* @param FromBuffer the packet serializer to read from
* @param ClientConn the client connection that sent the packet
*/
virtual void ProcessCancellationRequest(FNboSerializeFromBuffer& FromBuffer,FClientBeaconConnection& ClientConn);
/**
* Sends a client the specified response code
*
* @param Result the result being sent to the client
* @param ClientSocket the client socket to send the response on
*/
void SendReservationResponse(EPartyReservationResult Result,FSocket* ClientSocket);
/**
* Tells clients that a reservation update has occured and sends them the current
* number of remaining reservations so they can update their UI
*/
void SendReservationUpdates(void);
/**
* Initializes the team array so that random choices can be made from it
* Also initializes the host's team number (random from range)
*/
void InitTeamArray(void);
/**
* Determine if there are any teams that can fit the current party request.
*
* @param PartySize number of players in the party making a reservation request
* @return TRUE if there are teams available, FALSE otherwise
*/
virtual UBOOL AreTeamsAvailable(INT PartySize);
/**
* Determine the team number for the given party reservation request.
* Uses the list of current reservations to determine what teams have open slots.
*
* @param PartyRequest the party reservation request received from the client beacon
* @return index of the team to assign to all members of this party
*/
virtual INT GetTeamAssignment(const FPartyReservation& PartyRequest);
/**
* Readjust existing team assignments for party reservations in order to open the max
* available slots possible.
*/
void BestFitTeamAssignmentJiggle();
/**
* Determine the number of players on a given team based on current reservation entries
*
* @param TeamIdx index of team to search for
* @return number of players on a given team
*/
INT GetNumPlayersOnTeam(INT TeamIdx) const;
/**
* Find an existing player entry in a party reservation
*
* @param ExistingReservation party reservation entry
* @param PlayerMember member to search for in the existing party reservation
* @return index of party member in the given reservation, -1 if not found
*/
INT GetReservationPlayerMember(const FPartyReservation& ExistingReservation, const FUniqueNetId& PlayerMember) const;
/**
* Removes the specified party leader (and party) from the arrays and notifies
* any connected clients of the change in status
*
* @param PartyLeader the leader of the party to remove
* @param ClientConn the client connection that sent the packet
*/
void CancelPartyReservation(FUniqueNetId& PartyLeader,FClientBeaconConnection& ClientConn);
/**
* Determine if the reservation entry for a disconnected client should be removed.
*
* @param ClientConn the client connection that is disconnecting
* @return TRUE if the reservation for the disconnected client should be removed
*/
virtual UBOOL ShouldCancelReservationOnDisconnect(const FClientBeaconConnection& ClientConn);
/**
* Called whenever a new player is added to a reservation on this beacon
*
* @param PlayerRes player reservation entry that was added
*/
virtual void NewPlayerAdded(const FPlayerReservation& PlayerRes);
}
/**
* Pauses new reservation request on the beacon
*
* @param bPause if true then new reservation requests are denied
*/
native function PauseReservationRequests(bool bPause);
/**
* Creates a listening host beacon with the specified number of parties, players, and
* the session name that remote parties will be registered under
*
* @param InNumTeams the number of teams that are expected to join
* @param InNumPlayersPerTeam the number of players that are allowed to be on each team
* @param InNumReservations the total number of players to allow to join (if different than team * players)
* @param InSessionName the name of the session to add the players to when a reservation occurs
* @param InForceTeamNum the team to force to (only single team situations)
*
* @return true if the beacon was created successfully, false otherwise
*/
native function bool InitHostBeacon(int InNumTeams,int InNumPlayersPerTeam,int InNumReservations,name InSessionName, optional int InForceTeamNum=0);
/**
* Add a new party to the reservation list.
* Avoids adding duplicate entries based on party leader.
*
* @param PartyLeader the party leader that is adding the reservation
* @param PlayerMembers players (including party leader) being added to the reservation
* @param TeamNum team assignment of the new party
* @param bIsHost treat the party as the game host
* @return EPartyReservationResult similar to a client update request
*/
native function EPartyReservationResult AddPartyReservationEntry(UniqueNetId PartyLeader, const out array<PlayerReservation> PlayerMembers, int TeamNum, bool bIsHost);
/**
* Update a party with an existing reservation
* Avoids adding duplicate entries to the player members of a party.
*
* @param PartyLeader the party leader for which the existing reservation entry is being updated
* @param PlayerMembers players (not including party leader) being added to the existing reservation
* @return EPartyReservationResult similar to a client update request
*/
native function EPartyReservationResult UpdatePartyReservationEntry(UniqueNetId PartyLeader, const out array<PlayerReservation> PlayerMembers);
/**
* Find an existing reservation for the party leader
*
* @param PartyLeader the party leader to find a reservation entry for
* @return index of party leader in list of reservations, -1 if not found
*/
native function int GetExistingReservation(const out UniqueNetId PartyLeader);
/**
* Called when a player logs out of the current game. The player's
* party reservation entry is freed up so that a new reservation request
* can be accepted.
*
* @param PlayerId the net Id of the player that just logged out
* @param bMaintainParty if TRUE then preserve party members of a reservation when the party leader logs out
*/
native function HandlePlayerLogout(UniqueNetId PlayerId, bool bMaintainParty);
/**
* Called by the beacon when a reservation occurs or is cancelled so that UI can be updated, etc.
*/
delegate OnReservationChange();
/**
* Called by the beacon when all of the available reservations have been filled
*/
delegate OnReservationsFull();
/**
* Called by the beacon when a client cancels a reservation
*
* @param PartyLeader the party leader that is cancelling the reservation
*/
delegate OnClientCancellationReceived(UniqueNetId PartyLeader);
/**
* Stops listening for clients and releases any allocated memory
*/
native event DestroyBeacon();
/**
* Tells all of the clients to go to a specific session (contained in platform
* specific info). Used to route clients that aren't in the same party to one destination.
*
* @param SessionName the name of the session to register
* @param SearchClass the search that should be populated with the session
* @param PlatformSpecificInfo the binary data to place in the platform specific areas
*/
native function TellClientsToTravel(name SessionName,class<OnlineGameSearch> SearchClass,byte PlatformSpecificInfo[80]);
/**
* Tells all of the clients that the host is ready for them to travel to the host connection
*/
native function TellClientsHostIsReady();
/**
* Tells all of the clients that the host has cancelled the matchmaking beacon and that they
* need to find a different host
*/
native function TellClientsHostHasCancelled();
/**
* Determine if the beacon has filled all open reservation slots
*
* @return TRUE if all reservations have been consumed by party members
*/
function bool AreReservationsFull()
{
return NumConsumedReservations == NumReservations;
}
/**
* Registers all of the parties as part of the session that this beacon is associated with
*/
event RegisterPartyMembers()
{
local int Index;
local int PartyIndex;
local OnlineSubsystem OnlineSub;
local OnlineRecentPlayersList PlayersList;
local array<UniqueNetId> Members;
local PlayerReservation PlayerRes;
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
if (OnlineSub != None &&
OnlineSub.GameInterface != None)
{
// Iterate through the parties adding the players from each to the session
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
for (Index = 0; Index < Reservations[PartyIndex].PartyMembers.Length; Index++)
{
PlayerRes = Reservations[PartyIndex].PartyMembers[Index];
OnlineSub.GameInterface.RegisterPlayer(OnlineSessionName,PlayerRes.NetId,false);
Members.AddItem(PlayerRes.NetId);
}
// Add the remote party members to the recent players list if available
PlayersList = OnlineRecentPlayersList(OnlineSub.GetNamedInterface('RecentPlayersList'));
if (PlayersList != None)
{
PlayersList.AddPartyToRecentParties(Reservations[PartyIndex].PartyLeader,Members);
}
}
}
}
/**
* Unregisters each of the party members at the specified reservation with the session
*/
event UnregisterPartyMembers()
{
local int Index;
local int PartyIndex;
local OnlineSubsystem OnlineSub;
local PlayerReservation PlayerRes;
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
if (OnlineSub != None &&
OnlineSub.GameInterface != None)
{
// Iterate through the parties removing the players from each from the session
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
for (Index = 0; Index < Reservations[PartyIndex].PartyMembers.Length; Index++)
{
PlayerRes = Reservations[PartyIndex].PartyMembers[Index];
OnlineSub.GameInterface.UnregisterPlayer(OnlineSessionName,PlayerRes.NetId);
}
}
}
}
/**
* Unregisters each of the party members that have the specified party leader
*
* @param PartyLeader the leader to search for in the reservation array
*/
event UnregisterParty(UniqueNetId PartyLeader)
{
local int PlayerIndex;
local int PartyIndex;
local OnlineSubsystem OnlineSub;
local PlayerReservation PlayerRes;
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
if (OnlineSub != None &&
OnlineSub.GameInterface != None)
{
// Iterate through the parties removing the players from each from the session
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
// If this is the reservation in question, remove the members from the session
if (Reservations[PartyIndex].PartyLeader == PartyLeader)
{
for (PlayerIndex = 0; PlayerIndex < Reservations[PartyIndex].PartyMembers.Length; PlayerIndex++)
{
PlayerRes = Reservations[PartyIndex].PartyMembers[PlayerIndex];
OnlineSub.GameInterface.UnregisterPlayer(OnlineSessionName,PlayerRes.NetId);
}
}
}
}
}
/**
* Appends the skills from all reservations to the search object so that they can
* be included in the search information
*
* @param Search the search object to update
*/
native function AppendReservationSkillsToSearch(OnlineGameSearch Search);
/**
* Gathers all the unique ids for players that have reservations
*/
function GetPlayers(out array<UniqueNetId> Players)
{
local int PlayerIndex;
local int PartyIndex;
local PlayerReservation PlayerRes;
// Iterate through the parties adding the players from each to the out array
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
for (PlayerIndex = 0; PlayerIndex < Reservations[PartyIndex].PartyMembers.Length; PlayerIndex++)
{
PlayerRes = Reservations[PartyIndex].PartyMembers[PlayerIndex];
Players.AddItem(PlayerRes.NetId);
}
}
}
/**
* Gathers all the unique ids for party leaders that have reservations
*/
function GetPartyLeaders(out array<UniqueNetId> PartyLeaders)
{
local int PartyIndex;
// Iterate through the parties adding the players from each to the out array
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
PartyLeaders.AddItem(Reservations[PartyIndex].PartyLeader);
}
}
/**
* Determine the maximum team size that can be accommodated based
* on the current reservation slots occupied.
*
* @return maximum team size that is currently available
*/
native function int GetMaxAvailableTeamSize();
`if(`notdefined(FINAL_RELEASE))
/**
* Logs the reservation information for this beacon
*/
function DumpReservations()
{
local int PartyIndex;
local int MemberIndex;
local UniqueNetId NetId;
local PlayerReservation PlayerRes;
`Log("Debug info for Beacon: "$BeaconName,,'DevBeacon');
`Log("Session that reservations are for: "$OnlineSessionName,,'DevBeacon');
`Log("Number of teams: "$NumTeams,,'DevBeacon');
`Log("Number players per team: "$NumPlayersPerTeam,,'DevBeacon');
`Log("Number total reservations: "$NumReservations,,'DevBeacon');
`Log("Number consumed reservations: "$NumConsumedReservations,,'DevBeacon');
`Log("Number of party reservations: "$Reservations.Length,,'DevBeacon');
`Log("Reserved host team: "$ReservedHostTeamNum,,'DevBeacon');
// Log each party that has a reservation
for (PartyIndex = 0; PartyIndex < Reservations.Length; PartyIndex++)
{
NetId = Reservations[PartyIndex].PartyLeader;
`Log(" Party leader: "$class'OnlineSubsystem'.static.UniqueNetIdToString(NetId),,'DevBeacon');
`Log(" Party team: "$Reservations[PartyIndex].TeamNum,,'DevBeacon');
`Log(" Party size: "$Reservations[PartyIndex].PartyMembers.Length,,'DevBeacon');
// Log each member of the party
for (MemberIndex = 0; MemberIndex < Reservations[PartyIndex].PartyMembers.Length; MemberIndex++)
{
PlayerRes = Reservations[PartyIndex].PartyMembers[MemberIndex];
`Log(" Party member: "$class'OnlineSubsystem'.static.UniqueNetIdToString(PlayerRes.NetId)
$" skill: "$PlayerRes.Skill
$" xp: "$PlayerRes.XpLevel,,'DevBeacon');
}
}
`Log("",,'DevBeacon');
}
`endif

108
IpDrv/Classes/TcpLink.uc Normal file
View File

@ -0,0 +1,108 @@
//=============================================================================
// TcpLink: An Internet TCP/IP connection.
// Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
//=============================================================================
class TcpLink extends InternetLink
native
transient;
cpptext
{
ATcpLink();
void PostScriptDestroyed();
UBOOL Tick( FLOAT DeltaTime, enum ELevelTick TickType );
void CheckConnectionAttempt();
void CheckConnectionQueue();
void PollConnections();
UBOOL FlushSendBuffer();
void ShutdownConnection();
virtual UBOOL ShouldTickInEntry() { return true; }
virtual INT NativeReadBinary(INT Count, BYTE*& B);
}
//-----------------------------------------------------------------------------
// Variables.
// LinkState is only valid for TcpLink at this time.
var enum ELinkState
{
STATE_Initialized, // Sockets is initialized
STATE_Ready, // Port bound, ready for activity
STATE_Listening, // Listening for connections
STATE_Connecting, // Attempting to connect
STATE_Connected, // Open and connected
STATE_ListenClosePending,// Socket in process of closing
STATE_ConnectClosePending,// Socket in process of closing
STATE_ListenClosing, // Socket in process of closing
STATE_ConnectClosing // Socket in process of closing
} LinkState;
var IpAddr RemoteAddr; // Contains address of peer connected to from a Listen()
// If AcceptClass is not None, an actor of class AcceptClass will be spawned when an
// incoming connecting is accepted, leaving the listener open to accept more connections.
// Accepted() is called only in the child class. You can use the LostChild() and GainedChild()
// events to track your children.
var class<TcpLink> AcceptClass;
var const Array<byte> SendFIFO; // send fifo
var const string RecvBuf; // Recveive buffer (only used with MODE_Line)
//-----------------------------------------------------------------------------
// natives.
// BindPort: Binds a free port or optional port specified in argument one.
native function int BindPort( optional int PortNum, optional bool bUseNextAvailable );
// Listen: Listen for connections. Can handle up to 5 simultaneous connections.
// Returns false if failed to place socket in listen mode.
native function bool Listen();
// Open: Open a connection to a foreign host.
native function bool Open( IpAddr Addr );
// Close: Closes the current connection.
native function bool Close();
// IsConnected: Returns true if connected.
native function bool IsConnected();
// SendText: Sends text string.
// Appends a cr/lf if LinkMode=MODE_Line. Returns number of bytes sent.
native function int SendText( coerce string Str );
// SendBinary: Send data as a byte array.
native function int SendBinary( int Count, byte B[255] );
// ReadText: Reads text string.
// Returns number of bytes read.
native function int ReadText( out string Str );
// ReadBinary: Read data as a byte array.
native function int ReadBinary( int Count, out byte B[255] );
//-----------------------------------------------------------------------------
// Events.
// Accepted: Called during STATE_Listening when a new connection is accepted.
event Accepted();
// Opened: Called when socket successfully connects.
event Opened();
// Closed: Called when Close() completes or the connection is dropped.
event Closed();
// ReceivedText: Called when data is received and connection mode is MODE_Text.
event ReceivedText( string Text );
// ReceivedLine: Called when data is received and connection mode is MODE_Line.
// \r\n is stripped from the line
event ReceivedLine( string Line );
// ReceivedBinary: Called when data is received and connection mode is MODE_Binary.
event ReceivedBinary( int Count, byte B[255] );
defaultproperties
{
bAlwaysTick=True
}

View File

@ -0,0 +1,237 @@
/**
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
/**
* Local caching functionality for downloaded title files
*/
class TitleFileDownloadCache extends MCPBase
native
config(Engine)
implements(OnlineTitleFileCacheInterface)
dependson(OnlineSubsystem);
/** File operations being performed */
enum ETitleFileFileOp
{
TitleFile_None,
TitleFile_Save,
TitleFile_Load
};
/** Entry for a file that has been loaded/saved or is in the process of doing so */
struct native TitleFileCacheEntry extends OnlineSubsystem.TitleFile
{
/** Logical name to assign to the physical filename */
var string LogicalName;
/** CRC hash of the file that was read */
var string Hash;
/** Last file operation for this cached entry */
var ETitleFileFileOp FileOp;
/** Archive for loading/saving file. Only valid during async operation */
var private native const pointer Ar{class FArchive};
};
/** List of files that have been processed */
var array<TitleFileCacheEntry> TitleFiles;
/** The list of delegates to notify when a file is loaded */
var private array<delegate<OnLoadTitleFileComplete> > LoadCompleteDelegates;
/** The list of delegates to notify when a file is saved */
var private array<delegate<OnSaveTitleFileComplete> > SaveCompleteDelegates;
/**
* Starts an asynchronous read of the specified file from the local cache
*
* @param FileName the name of the file to read
*
* @return true if the calls starts successfully, false otherwise
*/
native function bool LoadTitleFile(string FileName);
/**
* Delegate fired when a file read from the local cache is complete
*
* @param bWasSuccessful whether the file read was successful or not
* @param FileName the name of the file this was for
*/
delegate OnLoadTitleFileComplete(bool bWasSuccessful,string FileName);
/**
* Adds the delegate to the list to be notified when a requested file has been read
*
* @param LoadCompleteDelegate the delegate to add
*/
function AddLoadTitleFileCompleteDelegate(delegate<OnLoadTitleFileComplete> LoadCompleteDelegate)
{
if (LoadCompleteDelegates.Find(LoadCompleteDelegate) == INDEX_NONE)
{
LoadCompleteDelegates[LoadCompleteDelegates.Length] = LoadCompleteDelegate;
}
}
/**
* Removes the delegate from the notify list
*
* @param LoadCompleteDelegate the delegate to remove
*/
function ClearLoadTitleFileCompleteDelegate(delegate<OnLoadTitleFileComplete> LoadCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = LoadCompleteDelegates.Find(LoadCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
LoadCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Starts an asynchronous write of the specified file to disk
*
* @param FileName the name of the file to save
* @param LogicalName the name to associate with the physical filename
* @param FileContents the buffer to write data from
*
* @return true if the calls starts successfully, false otherwise
*/
native function bool SaveTitleFile(string FileName,string LogicalName,array<byte> FileContents);
/**
* Delegate fired when a file save to the local cache is complete
*
* @param bWasSuccessful whether the file read was successful or not
* @param FileName the name of the file this was for
*/
delegate OnSaveTitleFileComplete(bool bWasSuccessful,string FileName);
/**
* Adds the delegate to the list to be notified when a requested file has been saved
*
* @param SaveCompleteDelegate the delegate to add
*/
function AddSaveTitleFileCompleteDelegate(delegate<OnSaveTitleFileComplete> SaveCompleteDelegate)
{
if (SaveCompleteDelegates.Find(SaveCompleteDelegate) == INDEX_NONE)
{
SaveCompleteDelegates[SaveCompleteDelegates.Length] = SaveCompleteDelegate;
}
}
/**
* Removes the delegate from the notify list
*
* @param SaveCompleteDelegate the delegate to remove
*/
function ClearSaveTitleFileCompleteDelegate(delegate<OnSaveTitleFileComplete> SaveCompleteDelegate)
{
local int RemoveIndex;
RemoveIndex = SaveCompleteDelegates.Find(SaveCompleteDelegate);
if (RemoveIndex != INDEX_NONE)
{
SaveCompleteDelegates.Remove(RemoveIndex,1);
}
}
/**
* Copies the file data into the specified buffer for the specified file
*
* @param FileName the name of the file to read
* @param FileContents the out buffer to copy the data into
*
* @return true if the data was copied, false otherwise
*/
native function bool GetTitleFileContents(string FileName,out array<byte> FileContents);
/**
* Determines the async state of the tile file read operation
*
* @param FileName the name of the file to check on
*
* @return the async state of the file read
*/
native function EOnlineEnumerationReadState GetTitleFileState(string FileName);
/**
* Determines the hash of the tile file that was read
*
* @param FileName the name of the file to check on
*
* @return the hash string for the file
*/
native function string GetTitleFileHash(string FileName);
/**
* Determines the hash of the tile file that was read
*
* @param FileName the name of the file to check on
*
* @return the logical name of the for the given physical filename
*/
native function string GetTitleFileLogicalName(string FileName);
/**
* Empties the set of cached files if possible (no async tasks outstanding)
*
* @return true if they could be deleted, false if they could not
*/
native function bool ClearCachedFiles();
/**
* Empties the cached data for this file if it is not being loaded/saved currently
*
* @param FileName the name of the file to remove from the cache
*
* @return true if it could be cleared, false if it could not
*/
native function bool ClearCachedFile(string FileName);
/**
* Deletes the set of title files from disc
*
* @param MaxAgeSeconds if > 0 then any files older than max seconds are deleted, if == 0 then all files are deleted
* @return true if they could be deleted, false if they could not
*/
native function bool DeleteTitleFiles(float MaxAgeSeconds);
/**
* Deletes a single file from disc
*
* @param FileName the name of the file to delete
*
* @return true if it could be deleted, false if it could not
*/
native function bool DeleteTitleFile(string FileName);
cpptext
{
/**
* Ticks any outstanding async tasks that need processing
*
* @param DeltaTime the amount of time that has passed since the last tick
*/
virtual void Tick(FLOAT DeltaTime);
/**
* Fires the delegates so the caller knows the file load/save is complete
*
* @param TitleFile the information for the file that was loaded/saved
* @param FileOp read/write opeartion on the file to know which delegates to call
*/
void TriggerDelegates(const FTitleFileCacheEntry* TitleFile,ETitleFileFileOp FileOp);
/**
* Searches the list of files for the one that matches the filename
*
* @param FileName the file to search for
*
* @return the file details
*/
FTitleFileCacheEntry* GetTitleFile(const FString& FileName);
/**
* @return base path to all cached files
*/
FString GetCachePath() const;
};

View File

@ -0,0 +1,179 @@
/**
* Data store provides access to available playlist resources
* The data for each playlist is provided through a data provider and is specified in the .ini file for that
* data provider class type using the PerObjectConfig paradigm.
*
* Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
*/
class UIDataStore_OnlinePlaylists extends UIDataStore
native(inherit)
Config(Game);
/** Constants used for accessing the data providers */
const RANKEDPROVIDERTAG = "PlaylistsRanked";
const UNRANKEDPROVIDERTAG = "PlaylistsUnranked";
const RECMODEPROVIDERTAG = "PlaylistsRecMode";
const PRIVATEPROVIDERTAG = "PlaylistsPrivate";
/** Name of provider class associated with this data store (uses them and all child classes) */
var config string ProviderClassName;
/** Class reference for the above provider class name */
var transient class<UIResourceDataProvider> ProviderClass;
/** Cached array of perobjectconfig data providers for playlists determined to be "ranked" */
var const array<UIResourceDataProvider> RankedDataProviders;
/** Cached array of perobjectconfig data providers for playlists determined to be "unranked" */
var const array<UIResourceDataProvider> UnrankedDataProviders;
/** Cached array of perobjectconfig data providers for playlists determined to be "recreational mode" */
var const array<UIResourceDataProvider> RecModeDataProviders;
/** Cached array of perobjectconfig data providers for playlists determined to be "private" */
var const array<UIResourceDataProvider> PrivateDataProviders;
/** The playlist to query about match details */
var OnlinePlaylistManager PlaylistMan;
cpptext
{
/* === UUIDataStore_GameResource interface === */
/**
* Finds or creates the UIResourceDataProvider instances used by online playlists, and stores the result by ranked or unranked provider types
*/
virtual void InitializeListElementProviders();
/* === UIDataStore interface === */
/**
* Calls the script event so that it can access the playlist manager
*/
virtual void InitializeDataStore()
{
Super::InitializeDataStore();
eventInit();
}
/**
* Loads the classes referenced by the ElementProviderTypes array.
*/
virtual void LoadDependentClasses();
/**
* Called when this data store is added to the data store manager's list of active data stores.
*
* @param PlayerOwner the player that will be associated with this DataStore. Only relevant if this data store is
* associated with a particular player; NULL if this is a global data store.
*/
virtual void OnRegister( ULocalPlayer* PlayerOwner );
/* === UObject interface === */
/** Required since maps are not yet supported by script serialization */
virtual void AddReferencedObjects( TArray<UObject*>& ObjectArray );
virtual void Serialize( FArchive& Ar );
/**
* Called from ReloadConfig after the object has reloaded its configuration data. Reinitializes the collection of list element providers.
*/
virtual void PostReloadConfig( UProperty* PropertyThatWasLoaded );
/**
* Callback for retrieving a textual representation of natively serialized properties. Child classes should implement this method if they wish
* to have natively serialized property values included in things like diffcommandlet output.
*
* @param out_PropertyValues receives the property names and values which should be reported for this object. The map's key should be the name of
* the property and the map's value should be the textual representation of the property's value. The property value should
* be formatted the same way that UProperty::ExportText formats property values (i.e. for arrays, wrap in quotes and use a comma
* as the delimiter between elements, etc.)
* @param ExportFlags bitmask of EPropertyPortFlags used for modifying the format of the property values
*
* @return return TRUE if property values were added to the map.
*/
virtual UBOOL GetNativePropertyValues( TMap<FString,FString>& out_PropertyValues, DWORD ExportFlags=0 ) const;
}
/**
* Grabs the playlist manager
*/
event Init()
{
local OnlineSubsystem OnlineSub;
OnlineSub = class'GameEngine'.static.GetOnlineSubsystem();
// Only download if we have a logged in player
if (OnlineSub != None &&
OnlineSub.Patcher != None)
{
PlaylistMan = OnlinePlaylistManager(OnlineSub.GetNamedInterface('PlaylistManager'));
}
}
/**
* Get the UIResourceDataProvider instances associated with the tag.
*
* @param ProviderTag the tag to find instances for; should match the ProviderTag value of an element in the ElementProviderTypes array.
* @param out_Providers receives the list of provider instances. this array is always emptied first.
*
* @return the list of UIResourceDataProvider instances registered for ProviderTag.
*/
native final function bool GetResourceProviders( name ProviderTag, out array<UIResourceDataProvider> out_Providers ) const;
/**
* Searches for resource provider instance given its associated provider tag and an index within that subset
*
* @param ProviderTag the name of the provider type; should match the ranked or unranked provider tag
* @param SearchField the index of the provider within the provider subset specified by the ProviderTag
* @param out_Provider the resource provider instance found
*
* @return true if the out_Provider has been found, false otherwise
*/
native final function bool GetPlaylistProvider( name ProviderTag, int ProviderIndex, out UIResourceDataProvider out_Provider);
/** Returns the OnlinePlaylistProvider with the corresponding PlaylistId */
static function OnlinePlaylistProvider GetOnlinePlaylistProvider( name ProviderTag, int PlaylistId, optional out int ProviderIndex )
{
local UIDataStore_OnlinePlaylists PlaylistDS;
local array<UIResourceDataProvider> Providers;
local OnlinePlaylistProvider OPP;
ProviderIndex = INDEX_NONE;
PlaylistDS = UIDataStore_OnlinePlaylists(class'UIRoot'.static.StaticResolveDataStore(class'UIDataStore_OnlinePlaylists'.default.Tag));
if ( PlaylistDS != None )
{
PlaylistDS.GetResourceProviders(ProviderTag, Providers);
for (ProviderIndex = 0; ProviderIndex < Providers.length; ProviderIndex++)
{
OPP = OnlinePlaylistProvider(Providers[ProviderIndex]);
if (OPP.PlaylistId == PlaylistId)
{
return OPP;
}
}
}
return None;
}
/**
* Returns the match type for the specified playlist
*
* @param PlaylistId the playlist we are searching for
*
* @return the match type for the playlist
*/
event int GetMatchTypeForPlaylistId(int PlaylistId)
{
if (PlaylistMan != None)
{
return PlaylistMan.GetMatchType(PlaylistId);
}
return -1;
}
DefaultProperties
{
Tag=OnlinePlaylists
}

View File

@ -0,0 +1,30 @@
/*=============================================================================
// WebApplication: Parent class for Web Server applications
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class WebApplication extends Object;
// Set by the webserver
var WorldInfo WorldInfo;
var WebServer WebServer;
var string Path;
function Init();
// This is a dummy function which should never be called
// Here for backwards compatibility
final function Cleanup();
function CleanupApp()
{
if (WorldInfo != None)
WorldInfo = None;
if (WebServer != None)
WebServer = None;
}
function bool PreQuery(WebRequest Request, WebResponse Response) { return true; }
function Query(WebRequest Request, WebResponse Response);
function PostQuery(WebRequest Request, WebResponse Response);

View File

@ -0,0 +1,297 @@
/*=============================================================================
WebConnection is the bridge that will handle all communication between
the web server and the client's browser.
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class WebConnection extends TcpLink config(Web);
var WebServer WebServer;
var string ReceivedData;
var WebRequest Request;
var WebResponse Response;
var WebApplication Application;
var bool bDelayCleanup;
var int RawBytesExpecting;
var config int MaxValueLength;
var config int MaxLineLength;
// MC: Debug
var int ConnID;
event Accepted()
{
WebServer = WebServer(Owner);
SetTimer(30, False);
ConnID = WebServer.ConnID++;
`Logd("Connection"@ConnID@"Accepted");
}
event Closed()
{
`Logd("Connection"@ConnID@"Closed");
Destroy();
}
event Timer()
{
`logd("Timer Up");
bDelayCleanup = False;
Cleanup();
}
event ReceivedText( string Text )
{
local int i;
local string S;
`logd("ReceivedText"@Text);
ReceivedData $= Text;
if(RawBytesExpecting > 0)
{
RawBytesExpecting -= Len(Text);
CheckRawBytes();
return;
}
// remove a LF which arrived in a new packet
// and thus didn't get cleaned up by the code below
if(Left(ReceivedData, 1) == Chr(10))
ReceivedData = Mid(ReceivedData, 1);
i = InStr(ReceivedData, Chr(13));
while(i != -1)
{
S = Left(ReceivedData, i);
i++;
// check for any LF following the CR.
if(Mid(ReceivedData, i, 1) == Chr(10))
i++;
ReceivedData = Mid(ReceivedData, i);
ReceivedLine(S);
if(LinkState != STATE_Connected)
return;
if(RawBytesExpecting > 0)
{
CheckRawBytes();
return;
}
i = InStr(ReceivedData, Chr(13));
}
}
function ReceivedLine(string S)
{
if (S == "")
{
EndOfHeaders();
}
else
{
if(Left(S, 4) ~= "GET ")
{
ProcessGet(S);
}
else if(Left(S, 5) ~= "POST ")
{
ProcessPost(S);
}
else if(Left(S, 5) ~= "HEAD ")
{
ProcessHead(S);
}
else if(Request != None)
{
Request.ProcessHeaderString(S);
}
}
}
function ProcessHead(string S)
{
`Logd("Received HEAD:"@S);
}
function ProcessGet(string S)
{
local int i;
`logd("Received GET:"@S);
if(Request == None)
CreateResponseObject();
Request.RequestType = Request_GET;
S = Mid(S, 4);
while(Left(S, 1) == " ")
S = Mid(S, 1);
i = InStr(S, " ");
if(i != -1)
S = Left(S, i);
i = InStr(S, "?");
if(i != -1)
{
Request.DecodeFormData(Mid(S, i+1));
S = Left(S, i);
}
Application = WebServer.GetApplication(S, Request.URI);
if(Application != None && Request.URI == "")
{
Response.Redirect(S$"/");
Cleanup();
}
else if(Application == None && Webserver.DefaultApplication != -1)
{
Response.Redirect(Webserver.ApplicationPaths[Webserver.DefaultApplication]$"/");
Cleanup();
}
}
function ProcessPost(string S)
{
local int i;
`logd("Received POST:"@S);
if(Request == None)
CreateResponseObject();
Request.RequestType = Request_POST;
S = Mid(S, 5);
while(Left(S, 1) == " ")
S = Mid(S, 1);
i = InStr(S, " ");
if(i != -1)
S = Left(S, i);
i = InStr(S, "?");
if(i != -1)
{
Request.DecodeFormData(Mid(S, i+1));
S = Left(S, i);
}
Application = WebServer.GetApplication(S, Request.URI);
if(Application != None && Request.URI == "")
{
// Response.Redirect(WebServer.ServerURL$S$"/");
Response.Redirect(S$"/");
Cleanup();
}
}
function CreateResponseObject()
{
local int i;
Request = new(None) class'WebRequest';
Request.RemoteAddr = IpAddrToString(RemoteAddr);
i = InStr(Request.RemoteAddr, ":");
if (i > -1)
{
Request.RemoteAddr = Left(Request.RemoteAddr, i);
}
Response = new(None) class'WebResponse';
Response.Connection = Self;
}
function EndOfHeaders()
{
if(Response == None)
{
CreateResponseObject();
Response.HTTPError(400); // Bad Request
Cleanup();
return;
}
if(Application == None)
{
Response.HTTPError(404); // FNF
Cleanup();
return;
}
if(Request.ContentLength != 0 && Request.RequestType == Request_POST)
{
RawBytesExpecting = Request.ContentLength;
RawBytesExpecting -= Len(ReceivedData);
CheckRawBytes();
}
else
{
if (Application.PreQuery(Request, Response))
{
Application.Query(Request, Response);
Application.PostQuery(Request, Response);
}
Cleanup();
}
}
function CheckRawBytes()
{
if(RawBytesExpecting <= 0)
{
if(InStr(Locs(Request.ContentType), "application/x-www-form-urlencoded") != 0)
{
`log("WebConnection: Unknown form data content-type: "$Request.ContentType);
Response.HTTPError(400); // Can't deal with this type of form data
}
else
{
Request.DecodeFormData(ReceivedData);
if (Application.PreQuery(Request, Response))
{
Application.Query(Request, Response);
Application.PostQuery(Request, Response);
}
ReceivedData = "";
}
Cleanup();
}
}
function Cleanup()
{
if (bDelayCleanup)
return;
if(Request != None)
Request = None;
if(Response != None)
{
Response.Connection = None;
Response = None;
}
if (Application != None)
Application = None;
Close();
}
final function bool IsHanging()
{
return bDelayCleanup;
}
defaultproperties
{
//MaxValueLength=512 // Maximum size of a variable value
//MaxLineLength=4096
}

170
IpDrv/Classes/WebRequest.uc Normal file
View File

@ -0,0 +1,170 @@
/*=============================================================================
// WebRequest: Parent class for handling/decoding web server requests
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class WebRequest extends Object
native;
enum ERequestType
{
Request_GET,
Request_POST
};
var string RemoteAddr;
var string URI;
var string Username;
var string Password;
var int ContentLength;
var string ContentType;
var ERequestType RequestType;
var private native const Map_Mirror HeaderMap{TMultiMap<FString, FString>}; // C++ placeholder.
var private native const Map_Mirror VariableMap{TMultiMap<FString, FString>}; // C++ placeholder.
native final function string DecodeBase64(string Encoded);
native final function string EncodeBase64(string Decoded);
native final function AddHeader(string HeaderName, coerce string Value);
native final function string GetHeader(string HeaderName, optional string DefaultValue);
native final function GetHeaders(out array<string> headers);
native final function AddVariable(string VariableName, coerce string Value);
native final function string GetVariable(string VariableName, optional string DefaultValue);
native final function int GetVariableCount(string VariableName);
native final function string GetVariableNumber(string VariableName, int Number, optional string DefaultValue);
native final function GetVariables(out array<string> varNames);
native final function Dump(); // only works in dev mode
function ProcessHeaderString(string S)
{
local int i;
//`log("ProcessHeaderString"@S);
if(Left(S, 21) ~= "Authorization: Basic ")
{
S = DecodeBase64(Mid(S, 21));
i = InStr(S, ":");
if(i != -1)
{
Username = Left(S, i);
Password = Mid(S, i+1);
//`log("User:"@Username@"Password:"@Password);
}
}
else if(Left(S, 16) ~= "Content-Length: ")
{
ContentLength = Int(Mid(S, 16, 64));
}
else if(Left(S, 14) ~= "Content-Type: ")
{
ContentType = Mid(S, 14);
}
i = InStr(S, ":");
if (i > -1)
{
AddHeader(Left(S, i), Mid(S, i+2)); // 2 = ": "
}
}
function DecodeFormData(string Data)
{
local string Token[2], ch;
local int i, H1, H2, limit;
local int t;
//`log("DecodeFormData"@Data);
t = 0;
for( i = 0; i < Len(Data); i++ )
{
if ( limit > class'WebConnection'.default.MaxValueLength || i > class'WebConnection'.default.MaxLineLength )
break;
ch = mid(Data, i, 1);
switch(ch)
{
case "+":
Token[t] $= " ";
limit++;
break;
case "&":
case "?":
if(Token[0] != "")
AddVariable(Token[0], Token[1]);
Token[0] = "";
Token[1] = "";
t = 0;
limit=0;
break;
case "=":
if(t == 0)
{
limit = 0;
t = 1;
}
else
{
Token[1] $= "=";
limit++;
}
break;
case "%":
H1 = GetHexDigit(Mid(Data, ++i, 1));
if ( H1 != -1 )
{
limit++;
H1 *= 16;
H2 = GetHexDigit(Mid(Data,++i,1));
if ( H2 != -1 )
Token[t] $= Chr(H1 + H2);
}
limit++;
break;
default:
Token[t] $= ch;
limit++;
}
}
if(Token[0] != "")
AddVariable(Token[0], Token[1]);
}
function int GetHexDigit(string D)
{
switch(caps(D))
{
case "0": return 0;
case "1": return 1;
case "2": return 2;
case "3": return 3;
case "4": return 4;
case "5": return 5;
case "6": return 6;
case "7": return 7;
case "8": return 8;
case "9": return 9;
case "A": return 10;
case "B": return 11;
case "C": return 12;
case "D": return 13;
case "E": return 14;
case "F": return 15;
}
return -1;
}
defaultproperties
{
}

View File

@ -0,0 +1,255 @@
/*=============================================================================
WebResponse is used by WebApplication to handle most aspects of sending
http information to the client. It serves as a bridge between WebApplication
and WebConnection.
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class WebResponse extends Object
native
config(Web);
/*
The correct order of sending a response is:
1. define content:
AddHeader(...), Subst(...), ClearSubst(...), LoadParsedUHTM(...), headers, CharSet,
2. HTTPResponse(...)
(optional, implies a 200 return when not explicitly send)
3. SendStandardHeaders(...)
(optional, implied by SendText(...))
4. send content:
IncludeUHTM(...), SendText(...)
*/
var array<string> headers; // headers send before the content
var private native const Map_Mirror ReplacementMap{TMultiMap<FString, FString>}; // C++ placeholder.
var const config string IncludePath;
var localized string CharSet;
var WebConnection Connection;
var protected bool bSentText; // used to warn headers already sent
var protected bool bSentResponse;
cpptext
{
private:
void SendInParts(const FString &S);
bool IncludeTextFile(const FString &Root, const FString &Filename, bool bCache=false, FString *Result = NULL);
bool ValidWebFile(const FString &Filename);
FString GetIncludePath();
//Hack to workaround script compiler code generation (mirrors the old code with fix)
void SendBinary(INT Count,BYTE* B)
{
WebResponse_eventSendBinary_Parms Parms(EC_EventParm);
Parms.Count=Count;
appMemcpy(&Parms.B,B,sizeof(Parms.B));
ProcessEvent(FindFunctionChecked(IPDRV_SendBinary),&Parms);
}
};
native final function bool FileExists(string Filename);
native final function Subst(string Variable, coerce string Value, optional bool bClear);
native final function ClearSubst();
native final function bool IncludeUHTM(string Filename);
native final function bool IncludeBinaryFile(string Filename);
native final function string LoadParsedUHTM(string Filename); // For templated web items, uses Subst too
native final function string GetHTTPExpiration(optional int OffsetSeconds);
native final function Dump(); // only works in dev mode
event SendText(string Text, optional bool bNoCRLF)
{
if(!bSentText)
{
SendStandardHeaders();
bSentText = True;
}
if(bNoCRLF)
{
Connection.SendText(Text);
}
else {
Connection.SendText(Text$Chr(13)$Chr(10));
}
}
event SendBinary(int Count, byte B[255])
{
Connection.SendBinary(Count, B);
}
function bool SendCachedFile(string Filename, optional string ContentType)
{
if(!bSentText)
{
SendStandardHeaders(ContentType, true);
bSentText = True;
}
return IncludeUHTM(Filename);
}
function FailAuthentication(string Realm)
{
HTTPError(401, Realm);
}
/**
* Send the HTTP response code.
*/
function HTTPResponse(string Header)
{
bSentResponse = True;
HTTPHeader(Header);
}
/**
* Will actually send a header. You should not call this method, queue the headers
* through the AddHeader() method.
*/
function HTTPHeader(string Header)
{
if(bSentText)
{
`Log("Can't send headers - already called SendText()");
}
else {
if (!bSentResponse)
{
HTTPResponse("HTTP/1.1 200 Ok");
}
if (Len(header) == 0)
{
bSentText = true;
}
Connection.SendText(Header$Chr(13)$Chr(10));
}
}
/**
* Add/update a header to the headers list. It will be send at the first possible occasion.
* To completely remove a given header simply give it an empty value, "X-Header:"
* To add multiple headers with the same name (need for Set-Cookie) you'll need
* to edit the headers array directly.
*/
function AddHeader(string header, optional bool bReplace=true)
{
local int i, idx;
local string part, entry;
i = InStr(header, ":");
if (i > -1)
{
part = Caps(Left(header, i+1)); // include the :
}
else {
return; // not a valid header
}
foreach headers(entry, idx)
{
if (InStr(Caps(entry), part) > -1)
{
if (bReplace)
{
if (i+2 >= len(header))
{
headers.remove(idx, 1);
}
else {
headers[idx] = header;
}
}
return;
}
}
if (len(header) > i+2)
{
// only add when it contains a value
headers.AddItem(Header);
}
}
/**
* Send the stored headers.
*/
function SendHeaders()
{
local string hdr;
foreach headers(hdr)
{
HTTPHeader(hdr);
}
}
function HTTPError(int ErrorNum, optional string Data)
{
switch(ErrorNum)
{
case 400:
HTTPResponse("HTTP/1.1 400 Bad Request");
SendText("<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD><BODY><H1>400 Bad Request</H1>If you got this error from a standard web browser, please mail epicgames.com and submit a bug report.</BODY></HTML>");
break;
case 401:
HTTPResponse("HTTP/1.1 401 Unauthorized");
AddHeader("WWW-authenticate: basic realm=\""$Data$"\"");
SendText("<HTML><HEAD><TITLE>401 Unauthorized</TITLE></HEAD><BODY><H1>401 Unauthorized</H1></BODY></HTML>");
break;
case 404:
HTTPResponse("HTTP/1.1 404 Not Found");
SendText("<HTML><HEAD><TITLE>404 File Not Found</TITLE></HEAD><BODY><H1>404 File Not Found</H1>The URL you requested was not found.</BODY></HTML>");
break;
default:
break;
}
}
/**
* Send the standard response headers.
*/
function SendStandardHeaders( optional string ContentType, optional bool bCache )
{
if(ContentType == "")
{
ContentType = "text/html";
}
if(!bSentResponse)
{
HTTPResponse("HTTP/1.1 200 OK");
}
AddHeader("Server: UnrealEngine IpDrv Web Server Build "$Connection.WorldInfo.EngineVersion, false);
AddHeader("Content-Type: "$ContentType, false);
if (bCache)
{
AddHeader("Cache-Control: max-age="$Connection.WebServer.ExpirationSeconds, false);
// Need to compute an Expires: tag .... arrgggghhh
AddHeader("Expires: "$GetHTTPExpiration(Connection.WebServer.ExpirationSeconds), false);
}
AddHeader("Connection: Close"); // always close
SendHeaders();
HTTPHeader("");
}
function Redirect(string URL)
{
HTTPResponse("HTTP/1.1 302 Document Moved");
AddHeader("Location: "$URL);
SendText("<html><head><title>Document Moved</title></head>");
SendText("<body><h1>Object Moved</h1>This document may be found <a HREF=\""$URL$"\">here</a>.</body></html>");
}
function bool SentText()
{
return bSentText;
}
function bool SentResponse()
{
return bSentResponse;
}
defaultproperties
{
//IncludePath="/Web"
//CharSet="iso-8859-1"
}

206
IpDrv/Classes/WebServer.uc Normal file
View File

@ -0,0 +1,206 @@
/*=============================================================================
WebServer is responsible for listening to requests on the selected http
port and will guide requests to the correct application.
Copyright 1998-2013 Epic Games, Inc. All Rights Reserved.
=============================================================================*/
class WebServer extends TcpLink config(Web);
var config string ServerName;
var config string Applications[10];
var config string ApplicationPaths[10];
var config bool bEnabled;
var config int ListenPort;
var config int MaxConnections;
var config int DefaultApplication;
var config int ExpirationSeconds; // How long images can be cached .. default is 24 hours
`if (`__TW_NETWORKING_)
var transient int CurrentListenPort;
`endif
var string ServerURL;
var WebApplication ApplicationObjects[10];
var int ConnectionCount;
// MC: Debug
var int ConnID;
function PostBeginPlay()
{
local int i;
local class<WebApplication> ApplicationClass;
local IpAddr l;
local string s;
// Destroy if not a server
if (WorldInfo.NetMode == NM_StandAlone || WorldInfo.NetMode == NM_Client)
{
Destroy();
return;
}
if(!bEnabled)
{
`Log("Webserver is not enabled. Set bEnabled to True in Advanced Options.");
Destroy();
return;
}
Super.PostBeginPlay();
if(ServerName == "")
{
GetLocalIP(l);
s = IpAddrToString(l);
i = InStr(s, ":");
if(i != -1)
s = Left(s, i);
ServerURL = "http://"$s;
}
else
ServerURL = "http://"$ServerName;
`if (`__TW_NETWORKING_)
CurrentListenPort = class'GameEngine'.static.GetWebAdminPort();
if (CurrentListenPort == 0)
{
CurrentListenPort = ListenPort;
}
`endif
`if (`__TW_NETWORKING_)
if(CurrentListenPort != 80)
ServerURL = ServerURL $ ":"$string(CurrentListenPort);
`else
if(ListenPort != 80)
ServerURL = ServerURL $ ":"$string(ListenPort);
`endif
`if (`__TW_NETWORKING_)
if (BindPort( CurrentListenPort ) > 0)
`else
if (BindPort( ListenPort ) > 0)
`endif
{
if (Listen() == true)
{
`if (`__TW_NETWORKING_)
`log("Web Server Created"@ServerURL@"Port:"@CurrentListenPort@"MaxCon"@MaxConnections@"ExpirationSecs"@ExpirationSeconds@"Enabled"@bEnabled);
`else
`log("Web Server Created"@ServerURL@"Port:"@ListenPort@"MaxCon"@MaxConnections@"ExpirationSecs"@ExpirationSeconds@"Enabled"@bEnabled);
`endif
//`log("~~~~~~~~~~~~~~~~~~~Loading Server Apps~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
for(i=0;i<10;i++)
{
if(Applications[i] == "")
break;
ApplicationClass = class<WebApplication>(DynamicLoadObject(Applications[i], class'Class'));
if(ApplicationClass != None)
{
//`log("Loading application"@Applications[i]@ApplicationClass@"Path:"@ApplicationPaths[i]);
ApplicationObjects[i] = New(None) ApplicationClass;
ApplicationObjects[i].WorldInfo = WorldInfo;
ApplicationObjects[i].WebServer = Self;
ApplicationObjects[i].Path = ApplicationPaths[i];
ApplicationObjects[i].Init();
}
else
{
`log("Failed to load"@Applications[i]);
}
}
//`log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
return;
}
else
{
`log("Unable to setup server for listen");
}
}
else
{
`log("Unable to bind webserver to a port");
}
//Fall through on failure, destroy ourselves
Destroy();
}
event Destroyed()
{
local int i;
`log("Destroying WebServer");
for(i = 0; i < ArrayCount(ApplicationObjects); i++)
{
if (ApplicationObjects[i] != None)
{
ApplicationObjects[i].CleanupApp();
}
}
Super.Destroyed();
}
event GainedChild( Actor C )
{
Super.GainedChild(C);
ConnectionCount++;
// if too many connections, close down listen.
if(MaxConnections > 0 && ConnectionCount > MaxConnections && LinkState == STATE_Listening)
{
`Log("WebServer: Too many connections - closing down Listen.");
Close();
}
}
event LostChild( Actor C )
{
Super.LostChild(C);
ConnectionCount--;
// if closed due to too many connections, start listening again.
if(ConnectionCount <= MaxConnections && LinkState != STATE_Listening)
{
`Log("WebServer: Listening again - connections have been closed.");
Listen();
}
}
function WebApplication GetApplication(string URI, out string SubURI)
{
local int i, l;
SubURI = "";
for(i=0;i<10;i++)
{
if(ApplicationPaths[i] != "")
{
l = Len(ApplicationPaths[i]);
if(Left(URI, l) ~= ApplicationPaths[i] && (Len(URI) == l || Mid(URI, l, 1) == "/"))
{
SubURI = Mid(URI, l);
`logd("Application handling request"@ApplicationObjects[i]@"Path:"@ApplicationPaths[i]);
return ApplicationObjects[i];
}
}
}
`log("No application found to handle request"@URI);
return None;
}
defaultproperties
{
//Applications(0)="xWebAdmin.UTServerAdmin"
//Applications(1)="xWebAdmin.UTImageServer"
//ApplicationPaths(0)="/ServerAdmin"
//ApplicationPaths(1)="/images"
//ListenPort=80
//MaxConnections=30
//ExpirationSeconds=86400
AcceptClass=Class'IpDrv.WebConnection'
}