235 lines
8.8 KiB
C++
235 lines
8.8 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Interfaces/OnlineSessionInterface.h"
|
|
#include "Subsystems/GameInstanceSubsystem.h"
|
|
#include "SessionSubsystem.generated.h"
|
|
|
|
/**
|
|
* Raison d'un echec, deja redigee pour etre affichee telle quelle.
|
|
*
|
|
* Il n'existe volontairement PAS de delegue de succes symetrique : quand
|
|
* l'hebergement ou la connexion reussit, la map change et tout ce qui ecoutait
|
|
* est detruit dans la seconde. Un delegue de succes serait donc soit jamais lu,
|
|
* soit lu par un widget qui n'existe deja plus.
|
|
*/
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSessionError, FText, Reason);
|
|
|
|
/** Fin d'une recherche. SessionCount vaut 0 si personne n'heberge. */
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSessionSearchFinished, bool, bWasSuccessful, int32, SessionCount);
|
|
|
|
/**
|
|
* La connexion a abouti, le voyage part dans un instant.
|
|
*
|
|
* Existe uniquement pour laisser le temps d'un fondu : le sous-systeme sait ou
|
|
* aller, mais le voile appartient au PlayerController -- et une invitation peut
|
|
* etre acceptee depuis le menu comme en pleine partie, donc depuis deux
|
|
* controllers differents. Chacun s'abonne et fond a sa maniere.
|
|
*/
|
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSessionTravelPending);
|
|
|
|
/**
|
|
* Hebergement et connexion : creer une partie, la trouver, la rejoindre.
|
|
*
|
|
* GameInstanceSubsystem et pas un composant : une session doit survivre au
|
|
* changement de map, puisque c'est justement elle qui le provoque. Le critere
|
|
* habituel du projet -- "si le pawn meurt, est-ce que ca doit survivre ?" --
|
|
* la place au meme endroit que les reglages.
|
|
*
|
|
* Le C++ ici ne parle JAMAIS de Steam. Tout passe par IOnlineSubsystem, dont
|
|
* l'implementation est choisie par DefaultEngine.ini. Consequence directe : le
|
|
* meme code marche en LAN via OnlineSubsystemNULL, ce qui permet de tester a
|
|
* deux instances sur la meme machine sans client Steam.
|
|
*/
|
|
UCLASS()
|
|
class SURVIVAL_PROJET_API USessionSubsystem : public UGameInstanceSubsystem
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
/** Coop a quatre : la valeur de reference du projet. */
|
|
static constexpr int32 DefaultMaxPlayers = 4;
|
|
|
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
|
virtual void Deinitialize() override;
|
|
|
|
/**
|
|
* Cree la session puis ouvre la map en serveur d'ecoute.
|
|
*
|
|
* C'est le sous-systeme qui charge la map et non l'appelant, parce que la
|
|
* creation est ASYNCHRONE : entre le clic et la reponse de Steam il peut
|
|
* s'ecouler une seconde, et ouvrir la map avant d'avoir la session donnerait
|
|
* une partie que personne ne peut rejoindre.
|
|
*
|
|
* En cas d'echec, rien ne bouge et OnHostFailed est diffuse.
|
|
*
|
|
* MaxPlayers n'a volontairement PAS de valeur par defaut : UHT ne sait pas
|
|
* resoudre une constante qualifiee dans une signature UFUNCTION, et ecrire
|
|
* "= 4" ici recreerait le second nombre a tenir en phase avec le GameMode.
|
|
* Les appelants passent USessionSubsystem::DefaultMaxPlayers.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Session")
|
|
void HostSession(const TSoftObjectPtr<UWorld>& Level, int32 MaxPlayers);
|
|
|
|
/** Cherche les parties joignables. Le resultat arrive par OnSearchFinished. */
|
|
UFUNCTION(BlueprintCallable, Category = "Session")
|
|
void FindSessions(int32 MaxResults = 20);
|
|
|
|
/**
|
|
* Rejoint un resultat de la derniere recherche, puis voyage vers l'hote.
|
|
* Un index hors bornes diffuse OnJoinFailed plutot que de ne rien faire.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Session")
|
|
void JoinFoundSession(int32 ResultIndex = 0);
|
|
|
|
/**
|
|
* Cherche puis rejoint la premiere partie trouvee, en une commande console.
|
|
*
|
|
* Aucun bouton ne mene ici : on ne rejoint une partie QUE par invitation
|
|
* Steam. Cette commande n'existe que pour pouvoir tester a deux instances
|
|
* sur une seule machine, ou aucune invitation n'est possible. La taper dans
|
|
* la console (touche ~) sur la seconde fenetre.
|
|
*/
|
|
UFUNCTION(Exec, Category = "Session")
|
|
void EmberJoin();
|
|
|
|
/**
|
|
* Ouvre le panneau d'invitation de l'overlay Steam.
|
|
*
|
|
* @return false si l'overlay est indisponible -- pas de service en ligne,
|
|
* pas de session, ou overlay desactive cote client Steam.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Session")
|
|
bool ShowInviteUI();
|
|
|
|
/**
|
|
* Peut-on inviter maintenant. Sert a griser le bouton plutot qu'a le laisser
|
|
* cliquer dans le vide : une partie lancee alors que Steam etait absent
|
|
* tourne tres bien, mais n'a personne a qui s'annoncer.
|
|
*/
|
|
UFUNCTION(BlueprintPure, Category = "Session")
|
|
bool CanInvite() const;
|
|
|
|
/**
|
|
* Quitte la session courante. A appeler en revenant au menu principal :
|
|
* une session laissee derriere soi empeche d'en creer une nouvelle, et
|
|
* laisse surtout ta partie annoncee a tes amis alors qu'elle n'existe plus.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Session")
|
|
void LeaveSession();
|
|
|
|
/** Vrai si une session est ouverte, qu'on l'heberge ou qu'on l'ait rejointe. */
|
|
UFUNCTION(BlueprintPure, Category = "Session")
|
|
bool IsSessionActive() const;
|
|
|
|
/**
|
|
* Nom du service en ligne reellement charge : "Steam", ou "NULL" en LAN.
|
|
* Sert a l'UI et surtout au diagnostic -- voir "NULL" alors qu'on attendait
|
|
* Steam est le symptome le plus frequent, et il se lit en une seconde.
|
|
*/
|
|
UFUNCTION(BlueprintPure, Category = "Session")
|
|
FString GetOnlineServiceName() const;
|
|
|
|
/** Combien de resultats la derniere recherche a rapportes. */
|
|
UFUNCTION(BlueprintPure, Category = "Session")
|
|
int32 GetFoundSessionCount() const;
|
|
|
|
UPROPERTY(BlueprintAssignable, Category = "Session")
|
|
FOnSessionError OnHostFailed;
|
|
|
|
UPROPERTY(BlueprintAssignable, Category = "Session")
|
|
FOnSessionError OnJoinFailed;
|
|
|
|
UPROPERTY(BlueprintAssignable, Category = "Session")
|
|
FOnSessionSearchFinished OnSearchFinished;
|
|
|
|
UPROPERTY(BlueprintAssignable, Category = "Session")
|
|
FOnSessionTravelPending OnTravelPending;
|
|
|
|
/**
|
|
* Duree laissee au fondu avant que le voyage parte.
|
|
*
|
|
* C'est LE sous-systeme qui la porte et non chaque controller : les deux
|
|
* doivent fondre exactement le temps qu'il attend, sinon l'ecran voyage
|
|
* avant d'etre noir. Une seule valeur, lue par tous.
|
|
*
|
|
* Pas de UPROPERTY editable : un GameInstanceSubsystem n'a aucun asset ou
|
|
* la regler. Si elle doit bouger un jour, ce sera ici.
|
|
*/
|
|
UFUNCTION(BlueprintPure, Category = "Session")
|
|
float GetTravelFadeDuration() const { return TravelFadeDuration; }
|
|
|
|
private:
|
|
void HandleCreateSessionComplete(FName SessionName, bool bWasSuccessful);
|
|
void HandleFindSessionsComplete(bool bWasSuccessful);
|
|
void HandleJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);
|
|
void HandleDestroySessionComplete(FName SessionName, bool bWasSuccessful);
|
|
|
|
/**
|
|
* L'ami a clique "Rejoindre la partie" dans l'overlay Steam.
|
|
*
|
|
* Ecoute des l'Initialize et jamais coupee : une invitation peut tomber a
|
|
* n'importe quel instant, y compris en pleine partie ou alors qu'aucun menu
|
|
* n'est affiche. Le sous-systeme est le seul objet dont on soit certain
|
|
* qu'il soit vivant a ce moment-la.
|
|
*/
|
|
void HandleInviteAccepted(const bool bWasSuccessful, const int32 ControllerId, FUniqueNetIdPtr UserId, const FOnlineSessionSearchResult& InviteResult);
|
|
|
|
/**
|
|
* Second temps de la commande EmberJoin : la recherche a rendu, on rejoint.
|
|
* UFUNCTION obligatoire, c'est ce qui permet le AddDynamic sur OnSearchFinished.
|
|
*/
|
|
UFUNCTION()
|
|
void HandleExecJoinSearchFinished(bool bWasSuccessful, int32 SessionCount);
|
|
|
|
/** La creation proprement dite, une fois le terrain degage. */
|
|
void CreateSessionNow();
|
|
|
|
void JoinSearchResult(const FOnlineSessionSearchResult& Result);
|
|
|
|
/**
|
|
* Vrai quand le service charge est OnlineSubsystemNULL, c'est-a-dire qu'on
|
|
* n'a pas Steam. Toutes les sessions basculent alors en LAN : c'est ce qui
|
|
* rend le test a deux instances possible sur une seule machine.
|
|
*/
|
|
bool IsLanMode() const;
|
|
|
|
/** Coupe proprement un delegue encore branche. Idempotent. */
|
|
void ClearPendingDelegates();
|
|
|
|
/** Le fondu a eu son temps : on voyage pour de bon. */
|
|
void CommitPendingTravel();
|
|
|
|
/** Adresse resolue de l'hote, en attente du fondu. */
|
|
FString PendingTravelURL;
|
|
|
|
FTimerHandle TravelTimer;
|
|
|
|
float TravelFadeDuration = 1.f;
|
|
|
|
IOnlineSessionPtr SessionInterface;
|
|
|
|
TSharedPtr<FOnlineSessionSearch> SearchSettings;
|
|
|
|
FDelegateHandle CreateSessionCompleteHandle;
|
|
FDelegateHandle FindSessionsCompleteHandle;
|
|
FDelegateHandle JoinSessionCompleteHandle;
|
|
FDelegateHandle DestroySessionCompleteHandle;
|
|
FDelegateHandle InviteAcceptedHandle;
|
|
|
|
/** Map a ouvrir une fois la session creee. */
|
|
TSoftObjectPtr<UWorld> PendingHostLevel;
|
|
|
|
int32 PendingMaxPlayers = DefaultMaxPlayers;
|
|
|
|
/**
|
|
* Une session trainait encore : on la detruit d'abord et la creation reprend
|
|
* dans le callback de destruction. Sans ce drapeau, quitter une partie puis
|
|
* en relancer une echoue silencieusement -- le moteur refuse deux sessions
|
|
* du meme nom, et l'erreur ne remonte nulle part.
|
|
*/
|
|
bool bCreateAfterDestroy = false;
|
|
};
|