// 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& 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 SearchSettings; FDelegateHandle CreateSessionCompleteHandle; FDelegateHandle FindSessionsCompleteHandle; FDelegateHandle JoinSessionCompleteHandle; FDelegateHandle DestroySessionCompleteHandle; FDelegateHandle InviteAcceptedHandle; /** Map a ouvrir une fois la session creee. */ TSoftObjectPtr 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; };