// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Interactable.h" #include "StorageContainer.generated.h" class UBoxComponent; class UInventoryComponent; class UItemDataAsset; class USoundBase; class UStaticMeshComponent; /** * Contenu initial d'un coffre pose dans le niveau. * * De la donnee de level design, pas du gameplay : un coffre de depart, une * cache a trouver. Verse une seule fois au BeginPlay, jamais relu ensuite. */ USTRUCT(BlueprintType) struct FStorageStartingItem { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage") TObjectPtr Item = nullptr; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1")) int32 Quantity = 1; }; /** * Coffre pose dans le monde. * * Il ne contient AUCUN modele de donnees a lui : son contenu est un * UInventoryComponent, exactement le meme que celui du joueur, simplement * configure sans barre rapide. C'est ce qui rend le glisser-deposer entre le * sac et le coffre gratuit -- ce sont les memes cases, le meme FInventorySlot, * et donc la durabilite d'une hache traverse le coffre sans une ligne de code. * * Toute la collision tient dans UNE boite, et les deux meshes n'en portent * aucune. C'est la difference avec ACraftingStation, qui se contente de son * mesh : un coffre est en deux morceaux dont l'un TOURNE. Se reposer sur leurs * collisions imposerait que le pack en fournisse deux correctes, et laisserait * surtout un trou dans la surface visable des que le couvercle se leve -- * viser le haut du coffre ouvert ne toucherait plus rien. * * La boite est fille de la caisse et ne bouge donc jamais : le coffre se vise * exactement pareil ouvert ou ferme. Elle bloque aussi le joueur physiquement, * ce qui evite d'avoir a se demander si le mesh du pack sait le faire. * * Aucun Tick au repos : il ne s'active que le temps d'animer le couvercle, et * se rendort des que l'angle est atteint. */ UCLASS() class SURVIVAL_PROJET_API AStorageContainer : public AActor, public IInteractable { GENERATED_BODY() public: AStorageContainer(); virtual void Tick(float DeltaTime) override; virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; /** Le couvercle vient de changer d'etat cote serveur : on joue l'animation. */ UFUNCTION() void OnRep_Open(); /** * Son et relance du tick d'animation. Partage par SetOpen (serveur) et * OnRep_Open (clients) : deux copies finiraient par diverger, et le * symptome serait un couvercle muet chez les invites. */ void ApplyOpenState(); // --- IInteractable --- virtual FText GetInteractionPrompt_Implementation(AActor* Interactor) const override; virtual void Interact_Implementation(AActor* Interactor) override; UFUNCTION(BlueprintPure, Category = "Storage") UInventoryComponent* GetStorage() const { return Storage; } /** Nom affiche en titre de l'ecran de coffre. */ UFUNCTION(BlueprintPure, Category = "Storage") FText GetDisplayName() const { return DisplayName; } /** Cases par ligne souhaitees pour la grille de l'ecran. */ UFUNCTION(BlueprintPure, Category = "Storage") int32 GetColumnCount() const { return ColumnCount; } /** * Ouvre ou ferme le couvercle. * * Appele par le controller, pas par le coffre lui-meme : c'est lui qui sait * quand l'ecran se referme, que ce soit par Echap, par la mort du joueur ou * parce qu'il s'est trop eloigne. Un coffre qui deciderait seul finirait * ouvert dans la moitie de ces cas. */ /** * Leve ou referme le couvercle. **Serveur uniquement** : le couvercle est * un etat du MONDE, les quatre joueurs doivent le voir bouger. Un client * qui l'appellerait verrait son coffre s'ouvrir tout seul chez lui. */ UFUNCTION(BlueprintCallable, Category = "Storage") void SetOpen(bool bInOpen); UFUNCTION(BlueprintPure, Category = "Storage") bool IsOpen() const { return bOpen; } protected: /** * Dimensionne le composant AVANT que son InitializeComponent() fige la * taille par defaut. PostInitializeComponents serait deja trop tard, et le * constructeur trop tot -- il ne connait pas les valeurs reglees sur * l'instance posee dans le niveau. */ virtual void PreInitializeComponents() override; virtual void BeginPlay() override; /** Redimensionne la boite quand un mesh change dans le Blueprint. */ virtual void OnConstruction(const FTransform& Transform) override; // ------------------------------------------------------------------ // Composants // ------------------------------------------------------------------ /** La caisse : SM_Chest_Bottom. Racine, purement visuelle. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage") TObjectPtr BaseMesh; /** * L'unique collision du coffre : ce que le trace d'interaction vient * toucher, et ce qui empeche le joueur de traverser. * * Fille de la caisse et non du couvercle : elle ne suit surtout pas ce * dernier. Un coffre ouvert doit se viser au meme endroit qu'un coffre * ferme, sinon le prompt disparait au moment precis ou l'on s'en sert. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage") TObjectPtr InteractionBox; /** * Le couvercle : SM_Chest_Top, enfant direct de la caisse. * * C'est LUI qu'on tourne, donc la charniere est le pivot que l'artiste a * donne au mesh -- un StaticMeshComponent tourne toujours autour de * l'origine de son mesh. Rien a placer a la main, mais rien a rattraper non * plus : si un pack posait ce pivot au centre du couvercle, l'ouverture se * verrait de travers et il faudrait corriger le pivot dans l'editeur de * mesh, pas ici. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage") TObjectPtr LidMesh; /** Le contenu. Un inventaire ordinaire, sans barre rapide. */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Storage") TObjectPtr Storage; // ------------------------------------------------------------------ // Reglages // ------------------------------------------------------------------ /** * Nombre de cases. Reglable PAR INSTANCE : le meme Blueprint donne un petit * coffre de 12 et un grand de 30 selon celui qu'on pose. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1", ClampMax = "60")) int32 SlotCount = 20; /** Cases par ligne dans l'ecran. Purement visuel. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage", meta = (ClampMin = "1")) int32 ColumnCount = 6; /** Nom affiche en titre de l'ecran. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage") FText DisplayName; /** Texte affiche quand le joueur vise le coffre. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage") FText InteractionPrompt; /** Verse dans le coffre au demarrage. Laisse vide pour un coffre a remplir. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage") TArray StartingItems; // ------------------------------------------------------------------ // Collision // ------------------------------------------------------------------ /** * La boite epouse toute seule les deux meshes, couvercle ferme. * * Coche par defaut : changer SM_Chest_v1 pour le v3 doit suffire, sans * avoir a retailler une boite a la main derriere. Decoche-le seulement si * tu veux une zone volontairement differente du volume du coffre. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Collision") bool bAutoFitInteractionBox = true; /** * Marge ajoutee autour des meshes par l'ajustement automatique. * * Garde-la petite. Le trace d'interaction est deja un balayage spherique * tolerant : gonfler la boite ne rendrait pas le coffre plus facile a viser, * ca l'empecherait juste de se coller a un mur. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Collision", meta = (ClampMin = "0", Units = "cm")) float InteractionBoxPadding = 0.f; // ------------------------------------------------------------------ // Couvercle // ------------------------------------------------------------------ /** * Rotation RELATIVE du couvercle ouvert. Le repos vaut toujours zero. * * Un FRotator complet et pas un simple angle : selon l'orientation dans * laquelle le mesh a ete exporte, la charniere peut tomber sur le pitch, le * yaw ou le roll. Regle les trois a l'oeil dans le viewport plutot que * d'aller comprendre quel axe local du mesh pointe ou. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Lid") FRotator OpenRotation = FRotator(-100.f, 0.f, 0.f); /** Duree de l'ouverture, en secondes. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Lid", meta = (ClampMin = "0.05", Units = "s")) float LidDuration = 0.35f; // ------------------------------------------------------------------ // Sons. Facultatifs : un champ vide ne joue rien. // ------------------------------------------------------------------ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Sound") TObjectPtr OpenSound; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Storage|Sound") TObjectPtr CloseSound; private: /** Recopie LidAlpha dans la rotation du couvercle. */ void ApplyLidRotation(); /** Cale la boite sur le volume combine des deux meshes, couvercle ferme. */ void FitInteractionBox(); /** * Repliqué à tout le monde : voir le couvercle d'un coffre se lever est le * seul signe qu'un coéquipier est en train de fouiller dedans. */ UPROPERTY(ReplicatedUsing = OnRep_Open) bool bOpen = false; /** 0 = ferme, 1 = ouvert. Interpole, jamais pose directement. */ float LidAlpha = 0.f; };