(Feat) Add Animation Fps Charater
This commit is contained in:
@@ -0,0 +1,478 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "SessionSubsystem.h"
|
||||
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Interfaces/OnlineExternalUIInterface.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "Online/OnlineSessionNames.h"
|
||||
#include "OnlineSessionSettings.h"
|
||||
#include "OnlineSubsystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "Session"
|
||||
|
||||
// Namespace NOMME et non anonyme : le build unity concatene les .cpp du module,
|
||||
// et deux constantes homonymes dans deux fichiers deviennent une redefinition
|
||||
// qui ne sort qu'au premier rebuild complet.
|
||||
namespace SessionSubsystemConstants
|
||||
{
|
||||
/**
|
||||
* Cle plantee dans les reglages de session et redemandee a la recherche.
|
||||
* Sans elle on tomberait sur les parties de n'importe quel autre jeu partageant
|
||||
* l'AppId de test 480 (Spacewar), qui sont legion.
|
||||
*/
|
||||
const FName GameKeyName(TEXT("EMBERWILD"));
|
||||
const TCHAR* GameKeyValue = TEXT("Coop");
|
||||
|
||||
/** Le service charge quand Steam n'est pas disponible. */
|
||||
const FName NullServiceName(TEXT("NULL"));
|
||||
}
|
||||
|
||||
void USessionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
IOnlineSubsystem* Online = IOnlineSubsystem::Get();
|
||||
if (!Online)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : aucun OnlineSubsystem charge, la coop sera indisponible."));
|
||||
return;
|
||||
}
|
||||
|
||||
SessionInterface = Online->GetSessionInterface();
|
||||
if (!SessionInterface.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : le service [%s] n'expose pas d'interface de session."), *Online->GetSubsystemName().ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
InviteAcceptedHandle = SessionInterface->AddOnSessionUserInviteAcceptedDelegate_Handle(
|
||||
FOnSessionUserInviteAcceptedDelegate::CreateUObject(this, &USessionSubsystem::HandleInviteAccepted));
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : service en ligne [%s], mode LAN [%d]."),
|
||||
*Online->GetSubsystemName().ToString(), IsLanMode() ? 1 : 0);
|
||||
}
|
||||
|
||||
void USessionSubsystem::Deinitialize()
|
||||
{
|
||||
if (SessionInterface.IsValid())
|
||||
{
|
||||
ClearPendingDelegates();
|
||||
SessionInterface->ClearOnSessionUserInviteAcceptedDelegate_Handle(InviteAcceptedHandle);
|
||||
}
|
||||
|
||||
SearchSettings.Reset();
|
||||
SessionInterface.Reset();
|
||||
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
bool USessionSubsystem::IsLanMode() const
|
||||
{
|
||||
const IOnlineSubsystem* Online = IOnlineSubsystem::Get();
|
||||
return !Online || Online->GetSubsystemName() == SessionSubsystemConstants::NullServiceName;
|
||||
}
|
||||
|
||||
FString USessionSubsystem::GetOnlineServiceName() const
|
||||
{
|
||||
const IOnlineSubsystem* Online = IOnlineSubsystem::Get();
|
||||
return Online ? Online->GetSubsystemName().ToString() : TEXT("None");
|
||||
}
|
||||
|
||||
bool USessionSubsystem::IsSessionActive() const
|
||||
{
|
||||
return SessionInterface.IsValid() && SessionInterface->GetNamedSession(NAME_GameSession) != nullptr;
|
||||
}
|
||||
|
||||
int32 USessionSubsystem::GetFoundSessionCount() const
|
||||
{
|
||||
return SearchSettings.IsValid() ? SearchSettings->SearchResults.Num() : 0;
|
||||
}
|
||||
|
||||
void USessionSubsystem::ClearPendingDelegates()
|
||||
{
|
||||
if (!SessionInterface.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteHandle);
|
||||
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteHandle);
|
||||
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
|
||||
SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteHandle);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Hebergement
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void USessionSubsystem::HostSession(const TSoftObjectPtr<UWorld>& Level, int32 MaxPlayers)
|
||||
{
|
||||
if (!SessionInterface.IsValid())
|
||||
{
|
||||
OnHostFailed.Broadcast(LOCTEXT("HostNoService", "No online service available."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Level.IsNull())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem::HostSession : aucune map fournie."));
|
||||
OnHostFailed.Broadcast(LOCTEXT("HostNoLevel", "No level configured for the game."));
|
||||
return;
|
||||
}
|
||||
|
||||
PendingHostLevel = Level;
|
||||
PendingMaxPlayers = FMath::Clamp(MaxPlayers, 1, 16);
|
||||
|
||||
// Une session survit a un retour au menu si personne ne l'a detruite. Le
|
||||
// moteur en refuse alors une seconde du meme nom, et l'echec est muet.
|
||||
if (SessionInterface->GetNamedSession(NAME_GameSession) != nullptr)
|
||||
{
|
||||
bCreateAfterDestroy = true;
|
||||
DestroySessionCompleteHandle = SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(
|
||||
FOnDestroySessionCompleteDelegate::CreateUObject(this, &USessionSubsystem::HandleDestroySessionComplete));
|
||||
|
||||
SessionInterface->DestroySession(NAME_GameSession);
|
||||
return;
|
||||
}
|
||||
|
||||
CreateSessionNow();
|
||||
}
|
||||
|
||||
void USessionSubsystem::CreateSessionNow()
|
||||
{
|
||||
FOnlineSessionSettings Settings;
|
||||
|
||||
Settings.NumPublicConnections = PendingMaxPlayers;
|
||||
Settings.NumPrivateConnections = 0;
|
||||
Settings.bIsLANMatch = IsLanMode();
|
||||
Settings.bShouldAdvertise = true;
|
||||
Settings.bAllowJoinInProgress = true;
|
||||
Settings.bAllowJoinViaPresence = true;
|
||||
Settings.bAllowInvites = true;
|
||||
Settings.bUsesPresence = true;
|
||||
|
||||
// LE drapeau a ne pas oublier. Steam ne route le trafic P2P que par un LOBBY :
|
||||
// sans lui la session est bien creee, elle apparait meme dans une recherche,
|
||||
// mais aucune connexion n'aboutit en dehors du LAN.
|
||||
Settings.bUseLobbiesIfAvailable = true;
|
||||
Settings.bUseLobbiesVoiceChatIfAvailable = false;
|
||||
|
||||
Settings.bUsesStats = false;
|
||||
Settings.bAntiCheatProtected = false;
|
||||
|
||||
Settings.Set(SETTING_MAPNAME, PendingHostLevel.GetAssetName(), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
|
||||
Settings.Set(SessionSubsystemConstants::GameKeyName, FString(SessionSubsystemConstants::GameKeyValue), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
|
||||
|
||||
CreateSessionCompleteHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(
|
||||
FOnCreateSessionCompleteDelegate::CreateUObject(this, &USessionSubsystem::HandleCreateSessionComplete));
|
||||
|
||||
// CreateSession rend false quand l'appel n'a meme pas pu partir : dans ce cas
|
||||
// le callback ne viendra jamais, il faut donc debrancher et repondre ici.
|
||||
if (!SessionInterface->CreateSession(0, NAME_GameSession, Settings))
|
||||
{
|
||||
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteHandle);
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem : CreateSession a ete refusee immediatement."));
|
||||
OnHostFailed.Broadcast(LOCTEXT("HostRefused", "Could not create the session."));
|
||||
}
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleCreateSessionComplete(FName SessionName, bool bWasSuccessful)
|
||||
{
|
||||
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteHandle);
|
||||
|
||||
if (!bWasSuccessful)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem : creation de la session [%s] echouee."), *SessionName.ToString());
|
||||
OnHostFailed.Broadcast(LOCTEXT("HostFailed", "Could not create the session."));
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : session [%s] creee pour %d joueurs."), *SessionName.ToString(), PendingMaxPlayers);
|
||||
|
||||
// "listen" fait de cette instance un serveur d'ecoute : l'hote joue ET
|
||||
// heberge dans le meme process, ce qui est exactement le modele voulu pour
|
||||
// une coop a quatre. Sans cette option la map s'ouvre en solo et les clients
|
||||
// se heurtent a un port ferme.
|
||||
UGameplayStatics::OpenLevelBySoftObjectPtr(GetGameInstance(), PendingHostLevel, /*bAbsolute=*/true, TEXT("listen"));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Recherche
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void USessionSubsystem::FindSessions(int32 MaxResults)
|
||||
{
|
||||
if (!SessionInterface.IsValid())
|
||||
{
|
||||
OnSearchFinished.Broadcast(false, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
SearchSettings = MakeShared<FOnlineSessionSearch>();
|
||||
SearchSettings->MaxSearchResults = FMath::Max(1, MaxResults);
|
||||
SearchSettings->bIsLanQuery = IsLanMode();
|
||||
|
||||
// Cote Steam les parties sont des lobbies, pas des serveurs annonces : sans
|
||||
// ce filtre la recherche part interroger le service de serveurs dedies et
|
||||
// ne rend jamais rien. En LAN la notion n'existe pas, on l'omet.
|
||||
if (!SearchSettings->bIsLanQuery)
|
||||
{
|
||||
SearchSettings->QuerySettings.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals);
|
||||
}
|
||||
|
||||
SearchSettings->QuerySettings.Set(SessionSubsystemConstants::GameKeyName, FString(SessionSubsystemConstants::GameKeyValue), EOnlineComparisonOp::Equals);
|
||||
|
||||
FindSessionsCompleteHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(
|
||||
FOnFindSessionsCompleteDelegate::CreateUObject(this, &USessionSubsystem::HandleFindSessionsComplete));
|
||||
|
||||
if (!SessionInterface->FindSessions(0, SearchSettings.ToSharedRef()))
|
||||
{
|
||||
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteHandle);
|
||||
OnSearchFinished.Broadcast(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleFindSessionsComplete(bool bWasSuccessful)
|
||||
{
|
||||
SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteHandle);
|
||||
|
||||
const int32 Count = GetFoundSessionCount();
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : recherche terminee (succes %d), %d partie(s)."), bWasSuccessful ? 1 : 0, Count);
|
||||
|
||||
OnSearchFinished.Broadcast(bWasSuccessful, Count);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Connexion
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void USessionSubsystem::JoinFoundSession(int32 ResultIndex)
|
||||
{
|
||||
if (!SearchSettings.IsValid() || !SearchSettings->SearchResults.IsValidIndex(ResultIndex))
|
||||
{
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinNoSession", "No game found to join."));
|
||||
return;
|
||||
}
|
||||
|
||||
JoinSearchResult(SearchSettings->SearchResults[ResultIndex]);
|
||||
}
|
||||
|
||||
void USessionSubsystem::EmberJoin()
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : EmberJoin, recherche d'une partie..."));
|
||||
|
||||
// On se branche pour un seul tour : la commande enchaine recherche puis
|
||||
// connexion, ce que le menu ne fait plus depuis que l'on ne rejoint que
|
||||
// par invitation.
|
||||
OnSearchFinished.AddDynamic(this, &USessionSubsystem::HandleExecJoinSearchFinished);
|
||||
FindSessions();
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleExecJoinSearchFinished(bool bWasSuccessful, int32 SessionCount)
|
||||
{
|
||||
OnSearchFinished.RemoveDynamic(this, &USessionSubsystem::HandleExecJoinSearchFinished);
|
||||
|
||||
if (!bWasSuccessful || SessionCount <= 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : EmberJoin n'a trouve aucune partie."));
|
||||
return;
|
||||
}
|
||||
|
||||
JoinFoundSession(0);
|
||||
}
|
||||
|
||||
bool USessionSubsystem::CanInvite() const
|
||||
{
|
||||
if (!IsSessionActive() || IsLanMode())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const IOnlineSubsystem* Online = IOnlineSubsystem::Get();
|
||||
return Online && Online->GetExternalUIInterface().IsValid();
|
||||
}
|
||||
|
||||
bool USessionSubsystem::ShowInviteUI()
|
||||
{
|
||||
IOnlineSubsystem* Online = IOnlineSubsystem::Get();
|
||||
if (!Online)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : pas de service en ligne, invitation impossible."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const IOnlineExternalUIPtr ExternalUI = Online->GetExternalUIInterface();
|
||||
if (!ExternalUI.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : le service [%s] n'a pas d'overlay."), *Online->GetSubsystemName().ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sans session ouverte, l'overlay s'affiche mais l'ami invite arrive sur
|
||||
// une partie qui n'existe pas. Mieux vaut refuser franchement.
|
||||
if (!IsSessionActive())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : aucune session ouverte, invitation impossible."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return ExternalUI->ShowInviteUI(0, NAME_GameSession);
|
||||
}
|
||||
|
||||
void USessionSubsystem::JoinSearchResult(const FOnlineSessionSearchResult& Result)
|
||||
{
|
||||
if (!SessionInterface.IsValid())
|
||||
{
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinNoService", "No online service available."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Result.IsValid())
|
||||
{
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinInvalid", "This game is no longer available."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Rejoindre alors qu'on heberge deja laisserait deux sessions ouvertes et
|
||||
// le voyage echouerait sans message.
|
||||
if (SessionInterface->GetNamedSession(NAME_GameSession) != nullptr)
|
||||
{
|
||||
SessionInterface->DestroySession(NAME_GameSession);
|
||||
}
|
||||
|
||||
JoinSessionCompleteHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(
|
||||
FOnJoinSessionCompleteDelegate::CreateUObject(this, &USessionSubsystem::HandleJoinSessionComplete));
|
||||
|
||||
if (!SessionInterface->JoinSession(0, NAME_GameSession, Result))
|
||||
{
|
||||
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinRefused", "Could not join this game."));
|
||||
}
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
|
||||
{
|
||||
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteHandle);
|
||||
|
||||
if (Result != EOnJoinSessionCompleteResult::Success)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem : JoinSession a echoue (code %d)."), static_cast<int32>(Result));
|
||||
|
||||
// On distingue le seul cas que le joueur peut corriger lui-meme du reste,
|
||||
// qui ne lui apprendrait rien.
|
||||
const FText Reason = (Result == EOnJoinSessionCompleteResult::SessionIsFull)
|
||||
? LOCTEXT("JoinFull", "This game is full.")
|
||||
: LOCTEXT("JoinFailed", "Could not join this game.");
|
||||
|
||||
OnJoinFailed.Broadcast(Reason);
|
||||
return;
|
||||
}
|
||||
|
||||
// L'adresse n'est connue qu'ici : c'est le service en ligne qui la fabrique,
|
||||
// et sous Steam ce n'est meme pas une IP mais un identifiant de relais.
|
||||
FString ConnectString;
|
||||
if (!SessionInterface->GetResolvedConnectString(SessionName, ConnectString))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem : adresse de l'hote introuvable."));
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinNoAddress", "Could not reach the host."));
|
||||
return;
|
||||
}
|
||||
|
||||
// On ne voyage PAS tout de suite : on annonce, on laisse le controller
|
||||
// courant fondre au noir, puis on part. Sans ce delai, accepter une
|
||||
// invitation depuis le menu coupe l'image d'un coup -- le seul endroit du
|
||||
// jeu ou une transition n'etait pas fondue.
|
||||
PendingTravelURL = ConnectString;
|
||||
OnTravelPending.Broadcast();
|
||||
|
||||
UGameInstance* OwningGameInstance = GetGameInstance();
|
||||
if (TravelFadeDuration <= 0.f || !OwningGameInstance)
|
||||
{
|
||||
CommitPendingTravel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Le TimerManager du GameInstance et non celui du monde : le monde courant
|
||||
// est justement celui qu'on s'apprete a quitter.
|
||||
OwningGameInstance->GetTimerManager().SetTimer(
|
||||
TravelTimer, this, &USessionSubsystem::CommitPendingTravel,
|
||||
TravelFadeDuration, /*bLoop=*/false);
|
||||
}
|
||||
|
||||
void USessionSubsystem::CommitPendingTravel()
|
||||
{
|
||||
if (PendingTravelURL.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FString TravelURL = MoveTemp(PendingTravelURL);
|
||||
PendingTravelURL.Reset();
|
||||
|
||||
APlayerController* Controller = GetGameInstance() ? GetGameInstance()->GetFirstLocalPlayerController() : nullptr;
|
||||
if (!Controller)
|
||||
{
|
||||
OnJoinFailed.Broadcast(LOCTEXT("JoinNoController", "Could not reach the host."));
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : voyage vers [%s]."), *TravelURL);
|
||||
Controller->ClientTravel(TravelURL, TRAVEL_Absolute);
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleInviteAccepted(const bool bWasSuccessful, const int32 ControllerId, FUniqueNetIdPtr UserId, const FOnlineSessionSearchResult& InviteResult)
|
||||
{
|
||||
if (!bWasSuccessful)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("USessionSubsystem : invitation acceptee mais illisible."));
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("USessionSubsystem : invitation acceptee, connexion en cours."));
|
||||
JoinSearchResult(InviteResult);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sortie
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void USessionSubsystem::LeaveSession()
|
||||
{
|
||||
if (!SessionInterface.IsValid() || SessionInterface->GetNamedSession(NAME_GameSession) == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Pas de reprise apres coup ici : on quitte, point.
|
||||
bCreateAfterDestroy = false;
|
||||
|
||||
DestroySessionCompleteHandle = SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(
|
||||
FOnDestroySessionCompleteDelegate::CreateUObject(this, &USessionSubsystem::HandleDestroySessionComplete));
|
||||
|
||||
SessionInterface->DestroySession(NAME_GameSession);
|
||||
}
|
||||
|
||||
void USessionSubsystem::HandleDestroySessionComplete(FName SessionName, bool bWasSuccessful)
|
||||
{
|
||||
SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteHandle);
|
||||
|
||||
if (!bCreateAfterDestroy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bCreateAfterDestroy = false;
|
||||
|
||||
if (!bWasSuccessful)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("USessionSubsystem : impossible de detruire la session [%s], hebergement annule."), *SessionName.ToString());
|
||||
OnHostFailed.Broadcast(LOCTEXT("HostStaleSession", "A previous game is still closing. Try again."));
|
||||
return;
|
||||
}
|
||||
|
||||
CreateSessionNow();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
Reference in New Issue
Block a user