//============================================================================= // KFOnlineLobbySteamworks //============================================================================= // Steam Lobby functionality for Killing Floor 2 //============================================================================= // Killing Floor 2 // Copyright (C) 2015 Tripwire Interactive LLC // - Joshua Rowan 9/25/2013 //============================================================================= class KFOnlineLobbySteamworks extends OnlineLobbyInterfaceSteamworks within OnlineSubsystemSteamworks config(Engine) native; `if(`STEAM_MATCHMAKING_LOBBY) /** Steam's Id for this lobby */ var UniqueNetId CurrentLobbyId; /** Empty Id to test for comparision with initialized ids */ var const UniqueNetId ZeroUniqueId; /** TRUE after MakeLobby and before OnCreateLobbyComplete */ var bool bCreatingLobby; /** If set, delay ShowLobbyInviteInterface until OnCreateLobbyComplete */ var bool bWaitingForLobby; /** TRUE after LobbyJoinGame and before LobbyJoinGameDelegate */ var bool bWaitingForServer; /** Holds server IP address while waiting for authentication to finish */ var string PendingServerIP; /** TRUE if logging is enabled */ var bool bDebug; /** Current lobby visibility */ var ELobbyVisibility LobbyVisibility; var UniqueNetId InviteLobbyId; cpptext { UBOOL GetServerAddr(DWORD& ip, INT& port); } function UniqueNetId GetCurrentLobbyId() { return CurrentLobbyId; } /********************************************************************************************* * @name Creating a Lobby ********************************************************************************************* */ /** Returns true if this client is connected to a steam lobby */ function bool IsInLobby() { if (CurrentLobbyId != ZeroUniqueId) { return true; } return false; } function bool IsLobbyOwner() { local bool success; local UniqueNetId OwnerID; if (!IsInLobby()) { `log(GetFuncName()@"Not in lobby", bDebug, 'DevLobby'); return false; } success = GetLobbyAdmin(CurrentLobbyId, OwnerID); `log(GetFuncName()@"success="$success@"CurrentLobbyId="$UniqueNetIdToString(CurrentLobbyId)@"OwnerID="$UniqueNetIdToString(OwnerID), bDebug, 'DevLobby'); return success && (OwnerID == LoggedInPlayerId); } function Initialize() { //MakeLobby(6, LV_Friends); AddLobbyInviteDelegate(OnLobbyInvite); AddLobbyJoinGameDelegate(OnLobbyJoinGame); AddLobbyReceiveMessageDelegate(OnLobbyReceiveMessage); } /** * Does some setup and calls the superclass implementation * * @param MaxPlayers The maximum number of lobby members * @param Type The type of lobby to setup (public/private/etc.) * @param InitialSettings The list of settings to apply to the lobby upon creation * @return Returns True if a lobby was created, False otherwise */ function bool MakeLobby(int MaxPlayers, ELobbyVisibility Type) { `log(GetFuncName()@"MaxPlayers="$MaxPlayers@"Type="$Type@"CurrentLobbyId="$UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); if ( IsInLobby() ) { // If we already have a lobby, use this to modify settings SetLobbyType(CurrentLobbyId, Type); return false; } AddCreateLobbyCompleteDelegate(OnCreateLobbyComplete); bCreatingLobby = true; return CreateLobby(MaxPlayers, Type); } /** * Called when 'CreateLobby' completes, returning success/failure, and (if successful) the new lobby UID * * @param bWasSuccessful whether or not 'CreateLobby' was successful * @param LobbyId If successful, the UID of the new lobby * @param Error If lobby creation failed, returns the error type */ function OnCreateLobbyComplete(bool bWasSuccessful, UniqueNetId LobbyId, string Error) { local string ConnectedServerIP; `log(GetFuncName()@"bWasSuccessful="$bWasSuccessful@"Id="$UniqueNetIdToString(LobbyId)@"Error="$Error, bDebug, 'DevLobby'); ClearCreateLobbyCompleteDelegate(OnCreateLobbyComplete); bCreatingLobby = false; if (bWasSuccessful) { AddLobbyMemberStatusUpdateDelegate(OnLobbyMemberStatusUpdate); CurrentLobbyId = LobbyId; ConnectedServerIP = GetConnectedServerIP(); if (ConnectedServerIP != "") { `log(GetFuncName()@"SetLobbyServer CurrentLobbyId="$UniqueNetIdToString(CurrentLobbyId)@"ConnectedServerIP="$ConnectedServerIP, bDebug, 'DevLobby'); SetLobbyServer(CurrentLobbyId, ZeroUniqueId, ConnectedServerIP); } if (bWaitingForLobby) { bWaitingForLobby = false; ShowLobbyInviteInterfaceInternal(); } NotifyLobbyStatusChanged(true); } } /********************************************************************************************* * @name Lobby Invites ********************************************************************************************* */ native function bool ShowLobbyInviteInterfaceInternal(); function ShowLobbyInviteInterface(string InviteMessage) { `log(GetFuncName()@"bCreatingLobby="$bCreatingLobby, bDebug, 'DevLobby'); if (bCreatingLobby) { bWaitingForLobby = true; } else { ShowLobbyInviteInterfaceInternal(); } } event bool InviteFriendToLobby(string Nickname) { local bool Success; local UniqueNetId FriendId; `log(GetFuncName()@"Nickname="$Nickname, bDebug, 'DevLobby'); if ( !IsInLobby() ) { return false; } FriendId = GetFriendUniqueId(Nickname); if (FriendId != ZeroUniqueId) { Success = InviteToLobby(CurrentLobbyId, FriendId); } if (Success) { `log("Friend" @ Nickname @ "with id" @ UniqueNetIdToString(FriendId) @ "invited to party" @ UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); } else { `log("Failed to invite Friend" @ nickname @ "to party" @ UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); } return Success; } event bool InviteFriendToLobbyByUid(QWORD Uid) { local bool Success; local UniqueNetId FriendId; if ( !IsInLobby() ) { return false; } FriendId.Uid = Uid; if (FriendId != ZeroUniqueId) { Success = InviteToLobby(CurrentLobbyId, FriendId); } if (Success) { `log("Friend with id" @ UniqueNetIdToString(FriendId) @ "invited to party" @ UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); } else { `log("Failed to invite Friend to party" @ UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); } return Success; } /** Finds a UniqueId from a Nickname */ function UniqueNetId GetFriendUniqueId(string Nickname, optional bool IncludeSelf = true) { local EOnlineEnumerationReadState readstate; local array Friends; local OnlineFriend i; if (IncludeSelf && Nickname == LoggedInPlayerName) { return LoggedInPlayerId; } readstate = GetFriendsList(0, Friends); if (readstate != OERS_Done) { `log("Do not have friends list loaded.", bDebug, 'DevLobby'); return ZeroUniqueId; } foreach Friends(i) { if (Caps(i.NickName) == Caps(Nickname)) { return i.UniqueId; } } `log("A user with nickname" @ Nickname @ "is not a friend.", bDebug, 'DevLobby'); return ZeroUniqueId; } /** Callback on lobby member join, quit, etc... */ function OnLobbyMemberStatusUpdate(const out array LobbyList, int LobbyIndex, int MemberIndex, int InstigatorIndex, string Status) { local UniqueNetId MemberId; local string Nickname; `log(GetFuncName()@"LobbyIndex="$LobbyIndex@"MemberIndex="$MemberIndex@"Status="$Status, bDebug, 'DevLobby'); MemberId = LobbyList[LobbyIndex].Members[MemberIndex].PlayerUID; if (MemberId != ZeroUniqueId) { Nickname = UniqueNetIdToPlayerName(MemberId); if (Nickname == "") { Nickname = "Unknown Nickname"; } `log(Nickname @ "(UID" @ UniqueNetIdToString(MemberId) $ ") status changed in party" @ UniqueNetIdToString(LobbyList[LobbyIndex].LobbyUID) $ ":" @ Status, bDebug, 'DevLobby'); } } function LobbyInvite(UniqueNetId LobbyId, UniqueNetId FriendId, bool bAccepted) { OnLobbyInvite(LobbyId, FriendId, bAccepted); } /** Client accepted/cancelled an invite from a lobby owner */ function OnLobbyInvite(UniqueNetId LobbyId, UniqueNetId FriendId, bool bAccepted) { `log(GetFuncName()@"LobbyId="$UniqueNetIdToString(LobbyId)@"FriendId="$UniqueNetIdToString(FriendId)@"bAccepted="$bAccepted, bDebug, 'DevLobby'); if (!bAccepted) { if (LobbyId != CurrentLobbyId) { InviteLobbyId = LobbyId; if(GetPC() != none) { GetPC().showInvitePopup(GetFriendNickName(FriendId, false), LobbyId, FriendId); } } return; //for now don't do anything upon receiving the invite } if (LobbyId == CurrentLobbyId) { `log(GetFuncName()@"Ignoring invitation to current lobby.", bDebug, 'DevLobby'); return; } if( GetPC() != none ) { `log(GetFuncName()@"Accepting lobby invite; disconnecting from current server.", bDebug, 'DevLobby'); GetPC().ConsoleCommand( "disconnect" ); } AddJoinLobbyCompleteDelegate(OnJoinLobbyComplete); JoinLobby(LobbyId); } function acceptInviteFromFriend() { `log(getFuncName()); if( GetPC() != none ) { `log(GetFuncName()@"Accepting lobby invite; disconnecting from current server.", bDebug, 'DevLobby'); GetPC().ConsoleCommand( "disconnect" ); } AddJoinLobbyCompleteDelegate(OnJoinLobbyComplete); `log(GetFuncName()@"LobbyId="$UniqueNetIdToString(InviteLobbyId)); JoinLobby(InviteLobbyId); } /** JoinLobby() finished */ function OnJoinLobbyComplete(bool bWasSuccessful, const out array LobbyList, int LobbyIndex, UniqueNetId LobbyUID, string Error) { local string ServerIPAddress; `log(GetFuncName()@"bWasSuccessful="$bWasSuccessful@"LobbyIndex="$LobbyIndex@"LobbyUID="$UniqueNetIdToString(LobbyUID)@"Error="$Error, bDebug, 'DevLobby'); ClearJoinLobbyCompleteDelegate(OnJoinLobbyComplete); if (bWasSuccessful) { // If we are in a game already, leave it and take us to the main lobby menu //ConsoleCommand("disconnect"); if ( IsInLobby() ) { LeaveLobby(CurrentLobbyId); } CurrentLobbyId = LobbyUID; AddLobbyMemberStatusUpdateDelegate(OnLobbyMemberStatusUpdate); ServerIPAddress = GetLobbyServerIP(CurrentLobbyId); `log(GetFuncName()@"ServerIPAddress="$ServerIPAddress, bDebug, 'DevLobby'); if ((class'WorldInfo'.static.IsEOSBuild() && ServerIPAddress != "" && ServerIPAddress != "NULL") || (!class'WorldInfo'.static.IsEOSBuild() && ServerIPAddress != "")) { `log(GetFuncName()@"JoinServer called", bDebug, 'DevLobby'); JoinServer(ServerIPAddress, ZeroUniqueId); QuitLobby(); } NotifyLobbyStatusChanged(true); } } /** Request to leave this lobby */ function bool QuitLobby() { local bool Success; `log(GetFuncName()@"CurrentLobbyId="$UniqueNetIdToString(CurrentLobbyId), bDebug, 'DevLobby'); if ( IsInLobby() ) { Success = LeaveLobby(CurrentLobbyId); CurrentLobbyId = ZeroUniqueId; ClearLobbyMemberStatusUpdateDelegate(OnLobbyMemberStatusUpdate); //MakeLobby(6, LV_Friends); } if(Success) { NotifyLobbyStatusChanged(false); } return success; } //@HSL_BEGIN - JRO - 7/16/2016 - Just use the PRI UniqueNetId on PC function UniqueNetId GetMyId() { local UniqueNetId LoggedInPlayer; GetUniquePlayerId(0, LoggedInPlayer); return LoggedInPlayer; } //@HSL_END /********************************************************************************************* * @name Game Invites ********************************************************************************************* */ native function bool JoinServer(string ServerIP, UniqueNetId ServerId); native function bool SetLobbyServer(UniqueNetId LobbyId, UniqueNetId ServerUID, string ServerIP); native function bool GetServerConnected(); native function string GetLobbyServerIP(const out UniqueNetId LobbyId); native function string GetConnectedServerIP(); native function string AppendPasswordToURL(string URL, string Password); native function RejectInvite(UniqueNetId LobbyId, UniqueNetId FriendId); /** Called on lobby owner when new listen server game is started */ function bool LobbyJoinGame(optional string ServerIP) { `log(GetFuncName()@"ServerIP="$GetLobbyServerIP(CurrentLobbyId), bDebug, 'DevLobby'); if ( !IsInLobby() ) { return false; } else if ( !GetServerConnected() || !Outer.CachedAuthInt.IsReady() ) { `log(GetFuncName()@"Server is not connected or auth is not ready.", bDebug, 'DevLobby'); if (!bWaitingForServer) { PendingServerIP = ServerIP; bWaitingForServer = true; Outer.CachedAuthInt.AddAuthReadyDelegate(LobbyJoinGameDelegate); `log(GetFuncName()@"Waiting for server PendingServerIP=" @ PendingServerIP, bDebug, 'DevLobby'); } return true; } //Make sure that delegate is always cleaned up, no matter what LobbyJoinGameDelegate(); return true; } /** Called when AuthReady is complete, or immediately if Auth is already ready */ function LobbyJoinGameDelegate() { local UniqueNetId ServerId; `log(GetFuncName()@"Done waiting for server PendingServerIP=" @ PendingServerIP, bDebug, 'DevLobby'); bWaitingForServer = false; Outer.CachedAuthInt.ClearAuthReadyDelegate(LobbyJoinGameDelegate); Outer.CachedAuthInt.GetServerUniqueId(ServerId); //Blank IP address means we own the lobby and we're joining our local listen server SetLobbyServer(CurrentLobbyId, ServerId, PendingServerIP); PendingServerIP = ""; } /* Called when a lobby member or owner joins a dedicated server, if a lobby owner, it takes the lobby, if not, leave the lobby */ function bool LobbyJoinServer(string ServerIP) { `log(GetFuncName()@"ServerIP="$ServerIP, bDebug, 'DevLobby'); if (IsLobbyOwner()) { //return LobbyJoinGame(ServerIP); return SetLobbyServer(CurrentLobbyId, ZeroUniqueId, ServerIP); } else { //If we're in a lobby but not the owner, leave the lobby since we're joining //a separate server QuitLobby(); return false; } } /** Called when the lobby owner starts a map */ function OnLobbyJoinGame(const out array LobbyList, int LobbyIndex, UniqueNetId ServerId, string ServerIP) { local bool bSuccess; if (IsLobbyOwner()) { return; } `log(GetFuncName()@"LobbyIndex="$LobbyIndex@"ServerId="$UniqueNetIdToString(ServerId)@"ServerIp="$ServerIp, bDebug, 'DevLobby'); //JoinServer handles the case of trying to join a server to which you are already connected //returns true in that case, and if the join attempt start successfully bSuccess = JoinServer(ServerIP, ServerId); QuitLobby(); `log(GetFuncName()@"bSuccess="$bSuccess@"ServerIP="$ServerIP, bDebug, 'DevLobby'); } /********************************************************************************************* * @name Lobby Matchmaking (aka Find Match) ********************************************************************************************* */ /** * Kicks off a search for available lobbies, matching the specified filters, triggering callbacks when done */ function bool FindLobbies(optional int MaxResults=32, optional array Filters, optional array SortFilters, optional int MinSlots, optional ELobbyDistance Distance=LD_Best) { AddFindLobbiesCompleteDelegate(FindLobbiesComplete); return super.FindLobbies(MaxResults, Filters, SortFilters, MinSlots, Distance); } /** Callback when FindLobbies finished */ function FindLobbiesComplete(bool bWasSuccessful, const out array LobbyList) { ClearFindLobbiesCompleteDelegate(FindLobbiesComplete); if (bWasSuccessful && LobbyList.length > 0) { AddJoinLobbyCompleteDelegate(OnJoinLobbyComplete); `log("Starting join of party" @ UniqueNetIdToString(LobbyList[0].LobbyUID)); JoinLobby(LobbyList[0].LobbyUID); } else if (bWasSuccessful) { `log("Unable to find a matching lobby."); NotifyUnsuccessfulSearch(); } else { `log("Find lobbies failed."); } } //@UI Notify the menu manager that the search was not successful function NotifyUnsuccessfulSearch() { local PlayerController PC; PC = GetPC(); if(PC != none) { PC.NotifyUnsuccessfulSearch(); } } function NotifyLobbyStatusChanged(bool bInLobby) { local PlayerController PC; PC = GetPC(); if(PC != none) { PC.OnLobbyStatusChanged(bInLobby); } } /********************************************************************************************* * @name Lobby Settings ********************************************************************************************* */ native function string GetLobbyData( int LobbyIndex, string Key ); function SetLobbyData( string Key, string Value ) { if ( !IsInLobby() ) { return; } SetLobbySetting(CurrentLobbyId, Key, Value); } function bool SetVisibility( int VisibilityIndex ) { if ( IsInLobby()) { LobbyVisibility = ELobbyVisibility(VisibilityIndex); return SetLobbyType( CurrentLobbyId, ELobbyVisibility(VisibilityIndex) ); } return false; } /********************************************************************************************* * @name Messages / Chat * @todo: We should be using LobbyData instead of messages to broadcast UI info about the lobby and it's members ********************************************************************************************* */ function PlayerController GetPC() { local WorldInfo WI; WI = class'WorldInfo'.static.GetWorldInfo(); return WI.GetALocalPlayerController(); } event ConsolePrint(string message) { GetPC().ClientMessage(message); } //event ConsoleCommand( string Command ) //{ // GetPC().ConsoleCommand( Command ); //} /** Recieve from other lobby member */ function OnLobbyReceiveMessage(const out array LobbyList, int LobbyIndex, int MemberIndex, string Type, string Message) { local UniqueNetId MemberId; local string Nickname; //`log(GetFuncName()@"LobbyIndex="$LobbyIndex@"MemberIndex="$MemberIndex@"Type="$Type@"Message="$Message, bDebug, 'DevLobbyMsg'); MemberId = LobbyList[LobbyIndex].Members[MemberIndex].PlayerUID; if (MemberId != ZeroUniqueId) { Nickname = UniqueNetIdToPlayerName(MemberId); if (Nickname == "") { Nickname = "Unknown Nickname"; } ConsolePrint(Message); } } /** Send message */ function bool LobbyMessage(string Message) { //`log(GetFuncName()@"Message="$Message, bDebug, 'DevLobbyMsg'); if ( !IsInLobby() ) { return false; } return SendLobbyMessage(CurrentLobbyId, Message); } /********************************************************************************************* * @name Debugging ********************************************************************************************* */ event TestFindLobbies() { `log("Attempting to find lobbies."); FindLobbies(); } function string GetLobbyURLString() { local ActiveLobbyInfo LobbyInfo; local string LobbyURLString; local LobbyMember Member; local int Index; if (!GetCurrentLobby(LobbyInfo)) { return ""; } foreach LobbyInfo.Members(Member, Index) { if (Member.PlayerUID != LoggedInPlayerId) { `log("LobbyURLString:"@UniqueNetIdToString(Member.PlayerUID)); LobbyURLString $= "?party"$Index$"=" $ UniqueNetIdToString(Member.PlayerUID); } } //LobbyURLString $= "?party3=0x0110000100122A11"; `log("LobbyURLString:"@LobbyURLString); return LobbyURLString; } function bool GetCurrentLobby(out ActiveLobbyInfo LobbyInfo) { local ActiveLobbyInfo i; if ( IsInLobby() ) { foreach ActiveLobbies(i) { if (i.LobbyUID == CurrentLobbyId) { LobbyInfo = i; return true; } } } return false; } function string GetFriendNickname(UniqueNetId FriendId, optional bool IncludeSelf = true) { local String Nickname; if (FriendId == ZeroUniqueId) { return ""; } //For convinience, consider yourself to be your own friend if (IncludeSelf && FriendId == LoggedInPlayerId) { return LoggedInPlayerName; } Nickname = UniqueNetIdToPlayerName(FriendId); if (Nickname == "") { Nickname = "Non-friend"; } return Nickname; } function SetServerPassword(string password) { if (IsLobbyOwner()) { SetLobbyData("Password", password); } } defaultproperties { bDebug=TRUE bWaitingForServer=FALSE } `endif