// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
// ESlateVisibility n'est que declare en avant par le moteur : il faut le type
// complet pour en stocker une valeur par defaut. Meme raison pour
// ECraftingStation, stocke en membre.
#include "CharacterAppearanceTypes.h"
#include "Components/SlateWrapperTypes.h"
#include "CraftingRecipeDataAsset.h"
#include "GameFramework/PlayerController.h"
#include "FpsPlayerController.generated.h"

class AStorageContainer;
class UConfirmDialogWidget;
class UCraftingComponent;
class UHotbarWidget;
class UInputAction;
class UInventoryComponent;
class UInteractionPromptWidget;
class UInventoryScreenWidget;
class UPauseMenuWidget;
class UScreenFadeComponent;
class USettingsMenuWidget;
class USurvivalStatsWidget;
class UUserWidget;
struct FInputActionValue;

/**
 * Controller du joueur. C'est ici que vit l'UI, et pas dans le personnage :
 * le controller survit a la mort du pawn, alors que le personnage non.
 * Si tu creais le HUD dans AFpsPlayer, il disparaitrait a chaque respawn.
 */
UCLASS()
class EMBERWILD_API AFpsPlayerController : public APlayerController
{
	GENERATED_BODY()

public:
	AFpsPlayerController();

	/** Ouvre ou ferme l'ecran, toujours sur l'onglet Inventaire. */
	UFUNCTION(BlueprintCallable, Category = "UI")
	void ToggleInventory();

	/**
	 * Ouvre l'ecran directement sur l'onglet Fabrication.
	 * Depuis l'onglet Inventaire elle bascule, depuis l'onglet Fabrication elle referme.
	 */
	UFUNCTION(BlueprintCallable, Category = "UI")
	void ToggleCrafting();

	/**
	 * Ouvre l'ecran de fabrication depuis un poste de travail.
	 *
	 * L'acces au poste ne dure QUE le temps de l'ecran : il est accorde ici et
	 * repris a la fermeture. C'est le contrat de l'interaction -- on fabrique a
	 * l'etabli parce qu'on est en train de s'en servir, pas parce qu'on en a
	 * croise un un jour.
	 *
	 * Le contrôleur s'en charge et non l'etabli : c'est lui qui sait quand
	 * l'ecran se referme, et lui seul peut donc rendre le poste au bon moment.
	 */
	UFUNCTION(BlueprintCallable, Category = "UI")
	void OpenCraftingAtStation(ECraftingStation Station);

	UFUNCTION(BlueprintPure, Category = "UI")
	bool IsInventoryOpen() const { return bInventoryOpen; }

	/**
	 * Ouvre l'ecran d'inventaire avec le panneau du coffre a cote : le sac a
	 * gauche, son contenu a droite.
	 *
	 * Ce n'est PAS un ecran a part. Le sac du joueur reste exactement au meme
	 * endroit qu'à l'ouverture par Tab -- c'est tout l'interet, un ecran
	 * distinct le ferait sauter a chaque fois qu'on ouvre un coffre.
	 *
	 * Comme pour l'etabli, c'est le controller qui pilote et non le coffre : lui
	 * seul sait quand l'ecran se referme, et donc quand refermer le couvercle.
	 */
	UFUNCTION(BlueprintCallable, Category = "UI|Storage")
	void OpenStorage(AStorageContainer* Container);

	/** Referme l'ecran de coffre. Sans effet s'il n'y en a pas d'ouvert. */
	UFUNCTION(BlueprintCallable, Category = "UI|Storage")
	void CloseStorage();

	UFUNCTION(BlueprintPure, Category = "UI|Storage")
	bool IsStorageOpen() const { return bStorageOpen; }

	/**
	 * Echap, quel que soit le chemin par lequel il arrive.
	 *
	 * Une seule touche pour cinq usages, d'ou une priorite explicite : boite de
	 * confirmation, reglages, coffre, inventaire, puis pause. Sans cet ordre on
	 * se retrouve avec deux ecrans ouverts en meme temps.
	 *
	 * Publique parce qu'elle a DEUX appelants : l'action Enhanced Input quand le
	 * viewport tient le focus clavier, et le NativeOnKeyDown d'un widget quand
	 * c'est lui qui l'a -- les touches n'atteignent alors plus l'input du jeu.
	 */
	void HandleEscapeInput();

	/** Ferme le menu de pause et relance le monde. */
	UFUNCTION(BlueprintCallable, Category = "UI|Pause")
	void ResumeGame();

	/** Ouvre la demande de confirmation. Ne quitte rien par elle-meme. */
	UFUNCTION(BlueprintCallable, Category = "UI|Pause")
	void RequestQuitToMainMenu();

	UFUNCTION(BlueprintPure, Category = "UI|Pause")
	bool IsPauseMenuOpen() const { return bPauseMenuOpen; }

	/** Ouvre l'ecran de reglages par-dessus le menu de pause. */
	UFUNCTION(BlueprintCallable, Category = "UI|Reglages")
	void OpenSettings();

	/**
	 * Ouvre le panneau d'invitation de l'overlay Steam.
	 *
	 * Passe par le controller et non directement du widget au sous-systeme :
	 * inviter est une action du JOUEUR LOCAL, et le controller est justement
	 * l'objet qui le represente. Un widget qui irait chercher un
	 * GameInstanceSubsystem tout seul se brancherait sur le mauvais joueur le
	 * jour ou l'ecran partage existera.
	 */
	UFUNCTION(BlueprintCallable, Category = "Coop")
	void ShowInviteFriends();

	/**
	 * Le point d'entree unique d'un changement d'apparence : persiste dans les
	 * reglages du joueur ET l'annonce au serveur.
	 *
	 * Les deux ensemble et jamais l'un sans l'autre -- persister sans annoncer
	 * laisserait le joueur seul a se voir change, annoncer sans persister le
	 * ferait repartir au visage par defaut au prochain lancement. C'est par ici
	 * que passent les commandes console, et par ici que passera l'ecran de
	 * customisation.
	 */
	UFUNCTION(BlueprintCallable, Category = "Apparence")
	void SubmitCharacterAppearance(const FCharacterAppearance& InAppearance);

	/** Faux quand Steam est absent ou qu'aucune session n'a pu etre creee. */
	UFUNCTION(BlueprintPure, Category = "Coop")
	bool CanInviteFriends() const;

protected:
	virtual void BeginPlay() override;

	/** Serveur uniquement. Chez un client, c'est AcknowledgePossession qui arrive. */
	virtual void OnPossess(APawn* InPawn) override;

	/**
	 * Le pendant CLIENT de OnPossess.
	 *
	 * OnPossess n'existe QUE sur le serveur : chez un client le pawn arrive par
	 * replication et cette fonction n'est jamais appelee. Comme c'est elle qui
	 * construisait le HUD, un joueur qui se connectait n'avait aucune interface --
	 * ni viseur, ni barre rapide, ni jauges. Le piege est d'autant plus fourbe
	 * que l'hote, lui, est bien le serveur et voit son HUD parfaitement.
	 */
	virtual void AcknowledgePossession(APawn* P) override;

	virtual void SetupInputComponent() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	// ------------------------------------------------------------------
	// Fondus
	//
	// La mécanique elle-même vit dans UScreenFadeComponent, partagé avec le
	// menu principal. Ici on ne garde que les durées propres au jeu.
	// La couleur et le WBP_Fade se règlent sur le composant.
	// ------------------------------------------------------------------

	/** Le voile. Assigne-lui ton WBP_Fade dans BP_FpsPlayerController. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UI|Fondu")
	TObjectPtr<UScreenFadeComponent> ScreenFade;

	/** Décoche pour démarrer sans fondu, pratique pendant les tests. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu")
	bool bFadeInOnStart = true;

	/**
	 * Délai maximal d'attente du pawn avant d'éclaircir quand même.
	 *
	 * Filet de sécurité pur : si la possession n'arrivait jamais, l'écran
	 * resterait noir pour toujours et le joueur croirait le jeu planté. Mieux
	 * vaut une vue bancale qu'un écran mort.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "1", Units = "s"))
	float StartupFadeTimeout = 10.f;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
	float StartupFadeDuration = 1.5f;

	/** Durée du fondu au noir quand le joueur meurt. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
	float DeathFadeDuration = 1.f;

	/**
	 * Temps total entre la mort et la réapparition.
	 * Doit rester supérieur à DeathFadeDuration, sinon le joueur réapparaît
	 * avant que l'écran soit entièrement noir.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
	float RespawnDelay = 3.f;

	/**
	 * Ouverture de l'inventaire, valeur Digital (bool).
	 * L'input d'interface vit sur le controller et non sur le pawn : ouvrir
	 * son sac doit rester possible meme sans corps a piloter.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> ToggleInventoryAction;

	/**
	 * Ouverture directe sur l'onglet Fabrication, valeur Digital (bool).
	 * Sur le controller comme ToggleInventoryAction : elle doit repondre alors
	 * meme que l'input du pawn est coupe, ecran ouvert.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> ToggleCraftingAction;

	/**
	 * Jette l'objet survole, valeur Digital (bool). Ctrl enfonce = un seul
	 * exemplaire au lieu de la pile entiere.
	 * Sur le controller aussi : le pawn a ses inputs coupes quand l'inventaire
	 * est ouvert, donc un binding sur le pawn ne repondrait jamais.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> DropItemAction;

	/**
	 * Selection directe d'un slot de la barre rapide, valeur Axis1D.
	 *
	 * Une seule action pour toutes les touches : dans IMC_Default, mappe les
	 * touches 1 a 6 sur cette action et donne a chacune un modificateur
	 * Scalar different (1, 2, 3...). Le handler lit la valeur et en deduit
	 * le slot. Six actions separees seraient six assets a maintenir.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> HotbarSelectAction;

	/** Molette de souris pour defiler dans la barre, valeur Axis1D. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> HotbarScrollAction;

	/**
	 * Echap : ferme l'inventaire s'il est ouvert, sinon bascule la pause.
	 *
	 * ATTENTION : coche "Trigger When Paused" sur l'asset IA_Pause. Un jeu en
	 * pause ne fait plus qu'un tick reduit ou seules les actions portant ce
	 * drapeau sont evaluees -- sans lui, on peut mettre en pause mais plus
	 * jamais en sortir au clavier.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
	TObjectPtr<UInputAction> PauseAction;

	/**
	 * Viseur, affiche en permanence. Widget separe du prompt : ce dernier se
	 * met en Collapsed des qu'on ne vise plus rien, et emporterait le viseur
	 * avec lui s'ils partageaient le meme widget.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSubclassOf<UUserWidget> CrosshairClass;

	/** A assigner dans BP_FpsPlayerController : ton WBP_InteractionPrompt. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSubclassOf<UInteractionPromptWidget> InteractionPromptClass;

	/**
	 * Ton WBP_InventoryScreen : l'ecran a onglets qui contient la grille
	 * d'inventaire ET la page de fabrication. Ce n'est plus WBP_Inventory
	 * directement, celui-ci est devenu une page a l'interieur.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSubclassOf<UInventoryScreenWidget> InventoryScreenClass;

	/**
	 * Distance au-dela de laquelle l'ecran de coffre se referme tout seul.
	 *
	 * Sans elle, on viderait un coffre depuis l'autre bout de la carte : rien
	 * n'oblige le joueur a rester devant une fois l'ecran ouvert.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Storage", meta = (ClampMin = "50", Units = "cm"))
	float MaxStorageDistance = 400.f;

	/** Ton WBP_Hotbar, affiche en permanence. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSubclassOf<UHotbarWidget> HotbarClass;

	/** Ton WBP_SurvivalStats : les trois jauges. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSubclassOf<USurvivalStatsWidget> SurvivalStatsClass;

	/** Ton WBP_PauseMenu. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
	TSubclassOf<UPauseMenuWidget> PauseMenuClass;

	/**
	 * Ton WBP_ConfirmDialog, le meme que celui du menu principal. Laisse-le
	 * assigne : sans lui on retourne au menu sans rien demander, et la partie
	 * est perdue sur un clic.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
	TSubclassOf<UConfirmDialogWidget> QuitToMenuDialogClass;

	/** La question posee avant d'abandonner la partie. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
	FText QuitToMenuMessage;

	/**
	 * Ton WBP_Settings, le meme que celui du menu principal. C'est tout
	 * l'interet de son delegue OnCloseRequested : l'ecran ignore qui l'ouvre.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Reglages")
	TSubclassOf<USettingsMenuWidget> SettingsMenuClass;

	/**
	 * Ta map de menu principal.
	 *
	 * TSoftObjectPtr comme dans AMainMenuPlayerController : une reference dure
	 * ferait charger la map du menu en meme temps que celle du jeu.
	 */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Pause")
	TSoftObjectPtr<UWorld> MainMenuLevel;

	/** Fondu au noir avant de rendre la main au menu principal. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI|Fondu", meta = (ClampMin = "0.1"))
	float QuitToMenuFadeDuration = 1.f;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
	TObjectPtr<UUserWidget> Crosshair;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
	TObjectPtr<UInteractionPromptWidget> InteractionPrompt;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
	TObjectPtr<UInventoryScreenWidget> InventoryScreen;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
	TObjectPtr<UHotbarWidget> Hotbar;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI")
	TObjectPtr<USurvivalStatsWidget> SurvivalStatsWidget;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Pause")
	TObjectPtr<UPauseMenuWidget> PauseMenu;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Pause")
	TObjectPtr<UConfirmDialogWidget> QuitToMenuDialog;

	UPROPERTY(Transient, BlueprintReadOnly, Category = "UI|Reglages")
	TObjectPtr<USettingsMenuWidget> SettingsMenu;

private:
	void CreateHudWidgets();

	/**
	 * Tout ce qu'il faut faire quand ce controller prend un pawn, quel que soit
	 * le cote du reseau : creer le HUD s'il manque, rebrancher les widgets sur
	 * le nouveau corps, ecouter sa mort.
	 *
	 * Appelee par OnPossess (serveur) ET AcknowledgePossession (client), donc
	 * potentiellement deux fois chez l'hote qui est les deux a la fois. Elle
	 * doit rester idempotente -- d'ou AddUniqueDynamic sur OnDied, et
	 * CreateHudWidgets qui ne cree que ce qui n'existe pas encore.
	 */
	void SetupForPossessedPawn(APawn* InPawn);

	/**
	 * Éclaircit l'écran d'arrivée, une seule fois, et seulement quand le joueur
	 * a réellement un corps.
	 *
	 * La condition n'est PAS « ai-je un pawn » mais « la caméra est-elle sur mon
	 * pawn ». Chez un client, le contrôleur commence sur un `SpectatorPawn` né
	 * au centre du monde -- sous le terrain -- et y reste encore un instant
	 * APRÈS `AcknowledgePossession`. Mesuré sur une vraie connexion : au moment
	 * où la possession est acquittée, `GetViewTarget()` vaut toujours
	 * `SpectatorPawn_0`. Éclaircir là montre le dessous de la carte.
	 *
	 * Tant que la caméra n'y est pas, on repasse toutes les `StartupFadeRetry`
	 * secondes. L'écran reste noir pendant ce temps -- le rendu du monde est
	 * suspendu par le GameInstance depuis le chargement de la map.
	 */
	void TryStartupFadeIn();

	/**
	 * Éclaircit pour de bon, sans vérifier la caméra.
	 * Appelée par le filet de sécurité : mieux vaut une vue bancale qu'un écran
	 * noir définitif, qui se lit comme un plantage.
	 */
	void CompleteStartupFadeIn();

	/** L'éclaircissement lui-même, après le temps de chauffe du monde. */
	void StartStartupFade();

	void OpenPauseMenu();

	/** Oui : fondu au noir, puis retour au menu principal. */
	UFUNCTION()
	void HandleQuitToMenuConfirmed();

	/**
	 * L'hote s'en va : ce client doit partir aussi.
	 *
	 * Une RPC explicite et non un simple abandon de connexion. Quand l'hote
	 * ferme sa partie, le moteur ne previent personne : les clients restent
	 * dans un monde mort, avec le pawn fantome de l'hote encore plante devant
	 * eux, jusqu'a un timeout qui peut ne jamais venir. On leur dit donc de
	 * partir, et ils partent avec leur propre fondu.
	 */
	UFUNCTION(Client, Reliable)
	void Client_ReturnToMainMenu();

	/**
	 * Lance le depart : ferme les ecrans, previent le GameInstance, fond au noir.
	 *
	 * Idempotente -- un client peut recevoir l'ordre de l'hote alors qu'il avait
	 * deja clique Quitter de son cote, et deux fondus simultanes se
	 * marcheraient dessus.
	 */
	void BeginQuitToMainMenu();

	/**
	 * Envoie l'ordre de depart a tous les AUTRES joueurs. Serveur uniquement.
	 *
	 * Appele AVANT le fondu de l'hote et non apres : les clients ont ainsi
	 * toute la duree de ce fondu pour recevoir le message et lancer le leur.
	 * Prevenir a la fin donnerait un client qui decouvre la coupure sans
	 * explication, ecran de jeu fige.
	 */
	void KickRemoteClientsToMainMenu();

	/** Non : on referme la boite et on rend la main au menu de pause. */
	UFUNCTION()
	void HandleQuitToMenuCancelled();

	/** Le fondu est termine : on charge la map du menu, si c'est ce qu'on attendait. */
	UFUNCTION()
	void HandleFadeFinished();

	/**
	 * Le joueur a accepte une invitation EN PLEINE PARTIE : on fond au noir
	 * avant que le voyage vers l'autre hote parte.
	 *
	 * bQuittingToMainMenu reste faux : ce n'est pas nous qui chargeons la map
	 * suivante, c'est USessionSubsystem. HandleFadeFinished doit donc laisser
	 * passer ce fondu-la sans rien faire.
	 */
	UFUNCTION()
	void HandleSessionTravelPending();

	/** L'ecran de reglages demande a se fermer : on rend la main au menu de pause. */
	UFUNCTION()
	void HandleSettingsCloseRequested();

	/**
	 * Point d'entree unique de l'ouverture et de la fermeture de l'ecran.
	 *
	 * Tab et la touche de fabrication passent tous les deux par ici : le mode
	 * d'input, la souris et la barre rapide sont ainsi decides a un seul endroit.
	 * Deux chemins separes finiraient par diverger.
	 */
	void SetInventoryScreenOpen(bool bOpen, bool bShowCraftingTab);

	/** Rend le poste accorde par le dernier OpenCraftingAtStation. Sans effet sinon. */
	void ReleaseActiveStation();

	/**
	 * Rend le coffre ouvert : couvercle referme, panneau masque, surveillance
	 * arretee. Sans effet s'il n'y en a pas.
	 *
	 * Separee de CloseStorage() pour la meme raison que ReleaseActiveStation :
	 * c'est la fermeture de l'ECRAN qui l'appelle, quelle qu'en soit la cause --
	 * Tab, Echap, la mort, un autre coffre. Un seul point de sortie, donc aucun
	 * chemin qui laisse un couvercle leve derriere soi.
	 */
	void ReleaseActiveStorage();

	/**
	 * Mode d'input et curseur pour un ecran plein cadre.
	 *
	 * Partage par l'inventaire et le coffre : deux copies de ces huit lignes
	 * finiraient par diverger, et le symptome -- une camera qui ne repond plus,
	 * un sprint bloque -- se cherche tres longtemps.
	 */
	void SetUiInputMode(bool bUiOpen);

	/** Le joueur s'est-il trop eloigne du coffre ouvert. Appele par un timer. */
	void CheckStorageDistance();

	/** Raccourci vers le composant de fabrication du pawn possede. */
	UCraftingComponent* GetPawnCrafting() const;

	/** L'ecran a change d'onglet : la barre rapide n'a rien a faire sous la page de craft. */
	UFUNCTION()
	void HandleInventoryTabChanged(bool bInventoryTabActive);

	/**
	 * Etat unique de la barre rapide : visible partout, sauf par-dessus l'onglet
	 * Fabrication. Passer par une seule fonction evite les cas ou la fermeture
	 * de l'ecran laisserait la barre cachee.
	 */
	void UpdateHotbarVisibility();

	/**
	 * Relaie le survol d'une case de la barre rapide vers le panneau de detail
	 * de l'ecran d'inventaire.
	 *
	 * Ignore ecran ferme : sans curseur il n'y a pas de survol, mais un widget
	 * masque sous la souris peut encore emettre sa sortie -- et decrire un objet
	 * dans un panneau invisible n'aurait aucun sens.
	 */
	UFUNCTION()
	void HandleHotbarSlotHovered(UInventoryComponent* Inventory, int32 SlotIndex, bool bHovered);

	/** Jette la pile survolee dans la grille, si l'inventaire est ouvert. */
	void DropHoveredItem();

	void HandleHotbarSelect(const FInputActionValue& Value);
	void HandleHotbarScroll(const FInputActionValue& Value);

	/** Raccourci vers l'inventaire du pawn possede. */
	UInventoryComponent* GetPawnInventory() const;

	/** Branche sur OnDied du pawn possede. */
	UFUNCTION()
	void HandlePawnDied();

	/**
	 * Detruit le cadavre et demande un nouveau pawn au GameMode.
	 *
	 * Le GameMode n'existe QUE sur le serveur : chez un client
	 * GetAuthGameMode() rend nullptr, toujours. Un client route donc par
	 * Server_RequestRespawn, et ne garde en local que son fondu de retour --
	 * c'est de l'affichage, ca lui appartient.
	 */
	void RespawnPlayer();

	UFUNCTION(Server, Reliable)
	void Server_RequestRespawn();

	// ------------------------------------------------------------------
	// Ouverture d'ecran demandee par un acteur du monde
	//
	// Interact_Implementation s'execute sur le SERVEUR depuis le passage en
	// coop. Or ces acteurs ouvrent une interface, qui n'existe que sur la
	// machine du joueur : l'appel doit donc repartir vers le client par une
	// RPC Client_. Sans elles, le serveur cree des widgets dans le vide et
	// l'ecran du joueur ne s'ouvre jamais.
	//
	// L'etat "devant quel poste je suis" est tenu des DEUX cotes : le client en
	// a besoin pour filtrer sa liste de recettes, le serveur pour valider le
	// craft. Ne le tenir que d'un cote laisserait soit une liste vide, soit un
	// etabli utilisable depuis la foret.
	// ------------------------------------------------------------------

	UFUNCTION(Client, Reliable)
	void Client_OpenCraftingAtStation(ECraftingStation Station);

	UFUNCTION(Client, Reliable)
	void Client_OpenStorage(AStorageContainer* Container);

	/** Le joueur a referme son ecran : le serveur reprend le poste accorde. */
	UFUNCTION(Server, Reliable)
	void Server_ReleaseStation(ECraftingStation Station);

	/** Le joueur a referme le coffre : le serveur rabat le couvercle. */
	UFUNCTION(Server, Reliable)
	void Server_ReleaseStorage(AStorageContainer* Container);

	/** Accorde le poste sur le composant du pawn et note lequel. */
	void GrantStation(ECraftingStation Station);

	// ------------------------------------------------------------------
	// Pseudo
	//
	// Le serveur ne peut PAS deviner le pseudo Steam d'un client : l'API
	// d'identite ne connait que l'utilisateur local de chaque machine. C'est
	// donc au client de l'annoncer. Sans ca, le moteur laisse le nom par
	// defaut du fichier de configuration -- on lit "Player" au-dessus des
	// quatre joueurs, ce qui se remarque tout de suite.
	// ------------------------------------------------------------------

	/** Le pseudo de l'utilisateur Steam local, ou une chaine vide si indisponible. */
	FString ResolveLocalPlayerNickname() const;

	/** Ecrit le pseudo sur le PlayerState, qui le replique a tout le monde. */
	UFUNCTION(Server, Reliable)
	void Server_SetPlayerNickname(const FString& Nickname);

	/**
	 * Meme chemin que le pseudo, et pour la meme raison : le serveur ne peut
	 * pas deviner l'apparence choisie par un client, elle vit dans le fichier
	 * de reglages de SA machine. C'est donc lui qui l'annonce.
	 *
	 * Treize octets envoyes une fois a la connexion -- l'apparence ne change
	 * pas en cours de partie, l'ecran de customisation vit dans le menu.
	 */
	UFUNCTION(Server, Reliable)
	void Server_SetCharacterAppearance(const FCharacterAppearance& InAppearance);

	/** Lit l'apparence persistee et l'envoie au serveur. Appelee au BeginPlay. */
	void AnnounceCharacterAppearance();

	FTimerHandle RespawnTimerHandle;

	/** Le fondu d'arrivée attend le pawn. */
	bool bStartupFadePending = false;

	FTimerHandle StartupFadeTimeoutHandle;

	/** Réessai tant que la caméra n'est pas sur le pawn. */
	FTimerHandle StartupFadeRetryHandle;


	/** Période de réessai, en secondes. Assez court pour ne pas se voir. */
	static constexpr float StartupFadeRetry = 0.05f;

	/** Surveillance de l'eloignement, actif seulement pendant qu'un coffre est ouvert. */
	FTimerHandle StorageDistanceTimerHandle;

	/** Le coffre actuellement ouvert, pour lui refermer son couvercle. */
	TWeakObjectPtr<AStorageContainer> ActiveStorage;

	bool bStorageOpen = false;

	bool bInventoryOpen = false;

	/** Onglet Fabrication affiche. N'a de sens que si bInventoryOpen est vrai. */
	bool bCraftingTabActive = false;

	/**
	 * Poste accorde par l'ecran actuellement ouvert, a rendre a la fermeture.
	 * Count = aucun, l'ecran a ete ouvert au clavier et pas depuis un etabli.
	 */
	ECraftingStation ActiveStation = ECraftingStation::Count;

	/**
	 * Visibilite d'origine de la barre rapide, relevee a sa creation.
	 * On la restaure telle quelle plutot que de forcer Visible : le Blueprint a
	 * peut-etre choisi SelfHitTestInvisible, et l'ecraser changerait
	 * silencieusement son comportement au clic.
	 */
	ESlateVisibility HotbarDefaultVisibility = ESlateVisibility::Visible;

	bool bPauseMenuOpen = false;

	/**
	 * La boite de confirmation avale Echap elle-meme quand elle a le focus,
	 * mais un clic ailleurs le lui retire : sans ce booleen, Echap refermerait
	 * alors le menu de pause en laissant la question affichee dans le vide.
	 */
	bool bQuitToMenuDialogOpen = false;

	bool bSettingsOpen = false;

	/** Distingue le fondu de sortie de tous les autres dans HandleFadeFinished. */
	bool bQuittingToMainMenu = false;
};
