(Feat) Add mulitplayer
This commit is contained in:
@@ -16,9 +16,14 @@
|
||||
#include "InteractionPromptWidget.h"
|
||||
#include "CraftingComponent.h"
|
||||
#include "InventoryScreenWidget.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "Interfaces/OnlineIdentityInterface.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "OnlineSubsystem.h"
|
||||
#include "PauseMenuWidget.h"
|
||||
#include "ScreenFadeComponent.h"
|
||||
#include "SessionSubsystem.h"
|
||||
#include "SettingsMenuWidget.h"
|
||||
#include "StorageContainer.h"
|
||||
#include "SurvivalStatsWidget.h"
|
||||
@@ -87,6 +92,23 @@ void AFpsPlayerController::BeginPlay()
|
||||
{
|
||||
SetInputMode(FInputModeGameOnly());
|
||||
SetShowMouseCursor(false);
|
||||
|
||||
// Annonce notre pseudo Steam. Seul le client connait le sien : l'API
|
||||
// d'identite ne renseigne que l'utilisateur local de chaque machine.
|
||||
const FString Nickname = ResolveLocalPlayerNickname();
|
||||
if (!Nickname.IsEmpty())
|
||||
{
|
||||
// L'hote est deja l'autorite : passer par la RPC marcherait, mais
|
||||
// l'appel direct evite un aller-retour pour rien.
|
||||
if (HasAuthority())
|
||||
{
|
||||
Server_SetPlayerNickname_Implementation(Nickname);
|
||||
}
|
||||
else
|
||||
{
|
||||
Server_SetPlayerNickname(Nickname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Le delegue se declenche a la fin de CHAQUE fondu : c'est
|
||||
@@ -143,6 +165,21 @@ void AFpsPlayerController::HandlePawnDied()
|
||||
}
|
||||
|
||||
void AFpsPlayerController::RespawnPlayer()
|
||||
{
|
||||
// Le fondu de retour est purement local : il appartient a l'ecran de ce
|
||||
// joueur, et il doit remonter meme si la reponse du serveur tarde.
|
||||
ScreenFade->FadeIn(StartupFadeDuration);
|
||||
|
||||
if (!HasAuthority())
|
||||
{
|
||||
Server_RequestRespawn();
|
||||
return;
|
||||
}
|
||||
|
||||
Server_RequestRespawn_Implementation();
|
||||
}
|
||||
|
||||
void AFpsPlayerController::Server_RequestRespawn_Implementation()
|
||||
{
|
||||
// On depossede AVANT de detruire : detruire un pawn encore possede laisse
|
||||
// le controller dans un etat incoherent le temps d'une frame.
|
||||
@@ -164,8 +201,6 @@ void AFpsPlayerController::RespawnPlayer()
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayerController : aucun GameMode, impossible de reapparaitre."));
|
||||
}
|
||||
|
||||
ScreenFade->FadeIn(StartupFadeDuration);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::SetupInputComponent()
|
||||
@@ -456,6 +491,32 @@ void AFpsPlayerController::OpenCraftingAtStation(ECraftingStation Station)
|
||||
return;
|
||||
}
|
||||
|
||||
// Appelee depuis Interact_Implementation, donc sur le SERVEUR. Pour un
|
||||
// client distant on accorde le poste ici -- c'est ce controller-la que
|
||||
// Server_Craft interrogera -- et on demande a l'ecran de s'ouvrir la-bas.
|
||||
// Chez un hote, IsLocalController() est vrai et on passe tout droit.
|
||||
if (!IsLocalController())
|
||||
{
|
||||
GrantStation(Station);
|
||||
Client_OpenCraftingAtStation(Station);
|
||||
return;
|
||||
}
|
||||
|
||||
GrantStation(Station);
|
||||
SetInventoryScreenOpen(true, /*bShowCraftingTab=*/true);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::Client_OpenCraftingAtStation_Implementation(ECraftingStation Station)
|
||||
{
|
||||
// On rejoue le chemin local : accorder le poste sur NOTRE copie du
|
||||
// composant, sans quoi la grille de recettes filtrerait sur {Hands} et
|
||||
// l'onglet paraitrait vide devant un etabli.
|
||||
GrantStation(Station);
|
||||
SetInventoryScreenOpen(true, /*bShowCraftingTab=*/true);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::GrantStation(ECraftingStation Station)
|
||||
{
|
||||
// Deja ouvert sur un autre poste : on rend le precedent avant d'accorder le
|
||||
// nouveau, sinon le compteur du composant ne redescendrait jamais.
|
||||
ReleaseActiveStation();
|
||||
@@ -465,8 +526,22 @@ void AFpsPlayerController::OpenCraftingAtStation(ECraftingStation Station)
|
||||
Crafting->AddAvailableStation(Station);
|
||||
ActiveStation = Station;
|
||||
}
|
||||
}
|
||||
|
||||
SetInventoryScreenOpen(true, /*bShowCraftingTab=*/true);
|
||||
void AFpsPlayerController::Server_ReleaseStation_Implementation(ECraftingStation Station)
|
||||
{
|
||||
if (UCraftingComponent* Crafting = GetPawnCrafting())
|
||||
{
|
||||
Crafting->RemoveAvailableStation(Station);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::Server_ReleaseStorage_Implementation(AStorageContainer* Container)
|
||||
{
|
||||
if (Container)
|
||||
{
|
||||
Container->SetOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AFpsPlayerController::ReleaseActiveStation()
|
||||
@@ -481,6 +556,14 @@ void AFpsPlayerController::ReleaseActiveStation()
|
||||
Crafting->RemoveAvailableStation(ActiveStation);
|
||||
}
|
||||
|
||||
// Le serveur tient sa propre liste de postes accessibles, celle qui sert a
|
||||
// valider Server_Craft. La laisser ouverte rendrait les recettes d'etabli
|
||||
// fabricables n'importe ou pour le reste de la partie.
|
||||
if (!HasAuthority())
|
||||
{
|
||||
Server_ReleaseStation(ActiveStation);
|
||||
}
|
||||
|
||||
ActiveStation = ECraftingStation::Count;
|
||||
}
|
||||
|
||||
@@ -600,7 +683,22 @@ void AFpsPlayerController::SetUiInputMode(bool bUiOpen)
|
||||
|
||||
void AFpsPlayerController::OpenStorage(AStorageContainer* Container)
|
||||
{
|
||||
if (!Container || !IsLocalController() || !InventoryScreen)
|
||||
if (!Container)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Appelee depuis Interact_Implementation, donc sur le SERVEUR. Le couvercle
|
||||
// est de l'etat de monde : il se leve ICI, et se replique aux quatre
|
||||
// joueurs. Seul l'ecran repart chez le client.
|
||||
if (!IsLocalController())
|
||||
{
|
||||
Container->SetOpen(true);
|
||||
Client_OpenStorage(Container);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InventoryScreen)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -613,6 +711,10 @@ void AFpsPlayerController::OpenStorage(AStorageContainer* Container)
|
||||
bStorageOpen = true;
|
||||
|
||||
InventoryScreen->ShowStorage(Container);
|
||||
|
||||
// SetOpen ne fait rien sans autorite : chez un client c'est la replication
|
||||
// de bOpen qui animera le couvercle. L'appel reste la pour l'hote, qui est
|
||||
// bien l'autorite et doit lever le sien tout de suite.
|
||||
Container->SetOpen(true);
|
||||
|
||||
// La barre rapide reste visible et cliquable par-dessus l'ecran : on lui
|
||||
@@ -634,6 +736,14 @@ void AFpsPlayerController::OpenStorage(AStorageContainer* Container)
|
||||
StorageDistanceCheckPeriod, /*bLoop=*/true);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::Client_OpenStorage_Implementation(AStorageContainer* Container)
|
||||
{
|
||||
// Chez le client IsLocalController() est vrai : OpenStorage prend donc sa
|
||||
// branche locale et ouvre reellement l'ecran. Le couvercle, lui, a deja ete
|
||||
// leve par le serveur et arrive par replication de bOpen.
|
||||
OpenStorage(Container);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::CloseStorage()
|
||||
{
|
||||
if (!bStorageOpen)
|
||||
@@ -659,7 +769,17 @@ void AFpsPlayerController::ReleaseActiveStorage()
|
||||
|
||||
if (AStorageContainer* Container = ActiveStorage.Get())
|
||||
{
|
||||
Container->SetOpen(false);
|
||||
// Chez un client SetOpen ne fait rien : le couvercle appartient au
|
||||
// serveur. On le lui demande, sinon le coffre resterait ouvert pour les
|
||||
// trois autres joueurs alors que l'ecran est referme ici.
|
||||
if (HasAuthority())
|
||||
{
|
||||
Container->SetOpen(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Server_ReleaseStorage(Container);
|
||||
}
|
||||
}
|
||||
ActiveStorage.Reset();
|
||||
|
||||
@@ -760,18 +880,18 @@ void AFpsPlayerController::OpenPauseMenu()
|
||||
bPauseMenuOpen = true;
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Visible);
|
||||
|
||||
// GameAndUI et non UIOnly, meme raison que l'inventaire : en UIOnly plus
|
||||
// aucune action Enhanced Input ne passe, et Echap ne refermerait rien.
|
||||
FInputModeGameAndUI Mode;
|
||||
Mode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
|
||||
Mode.SetHideCursorDuringCapture(false);
|
||||
SetInputMode(Mode);
|
||||
SetShowMouseCursor(true);
|
||||
|
||||
// Pas de DisableInput sur le pawn ici, contrairement a l'inventaire : le
|
||||
// monde est reellement fige, et les actions du pawn n'ont pas
|
||||
// "Trigger When Paused" -- elles ne se declenchent donc plus d'elles-memes.
|
||||
SetPause(true);
|
||||
// AUCUN SetPause : en coop la pause est impossible par nature. Cote client
|
||||
// l'appel est ignore -- la pause appartient a l'autorite -- et cote hote il
|
||||
// figerait le monde des quatre joueurs parce qu'un seul a ouvert son menu.
|
||||
// Le monde continue donc de tourner, comme dans Valheim ou Raft : ouvrir son
|
||||
// menu ne met pas a l'abri, et c'est coherent avec un jeu de survie.
|
||||
//
|
||||
// Consequence directe : il faut desormais couper l'input du PAWN, ce que la
|
||||
// pause faisait gratuitement. C'est exactement le travail de SetUiInputMode,
|
||||
// deja utilise par l'inventaire et le coffre -- un second chemin finirait
|
||||
// par diverger, et le symptome (camera qui tourne derriere le menu, sprint
|
||||
// bloque en sortant) se cherche tres longtemps.
|
||||
SetUiInputMode(true);
|
||||
}
|
||||
|
||||
void AFpsPlayerController::ResumeGame()
|
||||
@@ -800,10 +920,65 @@ void AFpsPlayerController::ResumeGame()
|
||||
PauseMenu->SetVisibility(ESlateVisibility::Collapsed);
|
||||
}
|
||||
|
||||
SetPause(false);
|
||||
SetUiInputMode(false);
|
||||
}
|
||||
|
||||
SetInputMode(FInputModeGameOnly());
|
||||
SetShowMouseCursor(false);
|
||||
FString AFpsPlayerController::ResolveLocalPlayerNickname() const
|
||||
{
|
||||
// On passe par l'interface generique, jamais par du Steam explicite : le
|
||||
// meme code rend le pseudo Steam en ligne et le nom local en LAN.
|
||||
if (const IOnlineSubsystem* Online = IOnlineSubsystem::Get())
|
||||
{
|
||||
const IOnlineIdentityPtr Identity = Online->GetIdentityInterface();
|
||||
if (Identity.IsValid())
|
||||
{
|
||||
const FString Nickname = Identity->GetPlayerNickname(0);
|
||||
if (!Nickname.IsEmpty())
|
||||
{
|
||||
return Nickname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Repli sans service en ligne : le nom du LocalPlayer, qui vaut le plus
|
||||
// souvent "Player". Mieux qu'une etiquette vide.
|
||||
if (const ULocalPlayer* LocalPlayerRef = GetLocalPlayer())
|
||||
{
|
||||
return LocalPlayerRef->GetNickname();
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
void AFpsPlayerController::Server_SetPlayerNickname_Implementation(const FString& Nickname)
|
||||
{
|
||||
APlayerState* State = GetPlayerState<APlayerState>();
|
||||
if (!State || Nickname.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Borne de longueur : un pseudo Steam monte a 32 caracteres, mais rien
|
||||
// n'empeche un client modifie d'en envoyer un de dix mille. On tronque
|
||||
// plutot que de refuser -- refuser laisserait une etiquette vide, tronquer
|
||||
// donne un nom lisible.
|
||||
State->SetPlayerName(Nickname.Left(32));
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("AFpsPlayerController : pseudo [%s]."), *State->GetPlayerName());
|
||||
}
|
||||
|
||||
void AFpsPlayerController::ShowInviteFriends()
|
||||
{
|
||||
if (USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr)
|
||||
{
|
||||
Sessions->ShowInviteUI();
|
||||
}
|
||||
}
|
||||
|
||||
bool AFpsPlayerController::CanInviteFriends() const
|
||||
{
|
||||
const USessionSubsystem* Sessions = GetGameInstance() ? GetGameInstance()->GetSubsystem<USessionSubsystem>() : nullptr;
|
||||
return Sessions && Sessions->CanInvite();
|
||||
}
|
||||
|
||||
void AFpsPlayerController::OpenSettings()
|
||||
@@ -957,11 +1132,6 @@ void AFpsPlayerController::HandleFadeFinished()
|
||||
return;
|
||||
}
|
||||
|
||||
// La pause vit sur les WorldSettings, que OpenLevel detruit avec le monde.
|
||||
// On la leve quand meme : un monde qu'on quitte en etat de pause est un
|
||||
// piege si le chargement echoue.
|
||||
SetPause(false);
|
||||
|
||||
UGameplayStatics::OpenLevelBySoftObjectPtr(this, MainMenuLevel);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user