Files
Unreal_EmberWild/Source/Survival_projet/Public/FpsPlayerController.h
T
Mathew 41d6e6a7f3 (Feat) Add Storage Chests + Item Details Panel
Coffres : AStorageContainer, un UInventoryComponent configure en conteneur,
affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui.
Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo.

Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la
grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple
(inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee
sous le curseur se voit.

Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre
rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la
fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme,
sinon passer de la barre a la grille viderait ce qui vient d'etre affiche.

L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory
etant instancie deux fois en mode coffre, une case a cocher par instance serait
un bug qui attend qu'on oublie de la decocher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:51:24 +02:00

438 lines
16 KiB
C++

// 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 "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 SURVIVAL_PROJET_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; }
/** 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();
protected:
virtual void BeginPlay() override;
virtual void OnPossess(APawn* InPawn) 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;
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();
/**
* Echap. Une seule touche pour trois usages, d'ou une priorite explicite :
* boite de confirmation, puis inventaire, puis pause. Sans cet ordre on se
* retrouve avec l'inventaire et la pause ouverts en meme temps.
*/
void HandlePauseInput();
void OpenPauseMenu();
/** Oui : fondu au noir, puis retour au menu principal. */
UFUNCTION()
void HandleQuitToMenuConfirmed();
/** 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();
/** 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. */
void RespawnPlayer();
FTimerHandle RespawnTimerHandle;
/** 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;
};