Files
Unreal_EmberWild/Source/Survival_projet/Public/SessionSubsystem.h
T
2026-08-03 10:30:47 +02:00

199 lines
7.6 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);
/**
* 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;
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();
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;
};