// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "InventoryComponent.generated.h" class UItemDataAsset; /** * Contenu d'un emplacement. Item nul = slot vide. * Le tableau de slots a toujours exactement la taille de l'inventaire : * l'index dans le tableau EST la position dans la grille, ce qui rend * l'affichage et le futur drag & drop triviaux. */ USTRUCT(BlueprintType) struct FInventorySlot { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire") TObjectPtr Item = nullptr; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire") int32 Quantity = 0; /** * Charges restantes de l'exemplaire courant, pour les objets a usages * multiples. Etat par instance, d'ou sa place ici et pas sur le DataAsset : * une canette entamee ne doit pas entamer toutes les canettes du jeu. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Inventaire") int32 RemainingUses = 0; bool IsEmpty() const { return Item == nullptr || Quantity <= 0; } void Clear() { Item = nullptr; Quantity = 0; RemainingUses = 0; } }; /** Diffuse a chaque modification du contenu ou de la taille. L'UI s'y abonne. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnInventoryChanged); /** Diffuse quand le joueur change de slot actif dans la barre rapide. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSelectedHotbarSlotChanged, int32, NewIndex); /** * Inventaire en grille a slots, facon Raft / Valheim. * * Le nombre de slots est dynamique : BaseSlotCount fixe le minimum, et * BonusSlotCount permet a un sac equipe de l'agrandir. Retirer le sac * refuse si les objets ne rentrent plus, plutot que de les faire * disparaitre en silence. */ UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent)) class SURVIVAL_PROJET_API UInventoryComponent : public UActorComponent { GENERATED_BODY() public: UInventoryComponent(); virtual void InitializeComponent() override; /** * Ajoute des objets, en remplissant d'abord les piles entamees. * @param RemainingUses charges de l'exemplaire ajoute ; -1 signifie "plein". * Sert a rendre son etat a un objet ramasse au sol. * @return le nombre d'exemplaires qui N'ONT PAS pu rentrer (0 = tout est passe). */ UFUNCTION(BlueprintCallable, Category = "Inventaire") int32 AddItem(UItemDataAsset* Item, int32 Quantity = 1, int32 RemainingUses = -1); /** * Consomme une utilisation du slot. * Retire un exemplaire quand il ne reste plus de charge. * @return true si quelque chose a ete consomme. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool ConsumeUse(int32 SlotIndex); /** * Retire des objets, en vidant d'abord les piles les plus petites. * @return le nombre d'exemplaires reellement retires. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") int32 RemoveItem(UItemDataAsset* Item, int32 Quantity = 1); /** Combien d'exemplaires de cet objet, toutes piles confondues. */ UFUNCTION(BlueprintPure, Category = "Inventaire") int32 GetItemCount(const UItemDataAsset* Item) const; /** Combien d'exemplaires pourraient encore rentrer, sans rien modifier. */ UFUNCTION(BlueprintPure, Category = "Inventaire") int32 GetRoomFor(const UItemDataAsset* Item) const; /** * Un seul tableau, deux conteneurs distincts : * index 0 .. HotbarSlotCount-1 -> barre rapide * index HotbarSlotCount .. fin -> grille d'inventaire (+ bonus de sac) * * Un objet range dans la barre n'apparait PAS dans la grille, et * inversement. Ils partagent le meme tableau uniquement pour que le * glisser-deposer entre les deux ne demande aucune conversion d'index. */ UFUNCTION(BlueprintPure, Category = "Inventaire") int32 GetTotalSlotCount() const { return FMath::Max(0, HotbarSlotCount + BackpackSlotCount + BonusSlotCount); } /** Premier index affiche par la grille, juste apres la barre rapide. */ UFUNCTION(BlueprintPure, Category = "Inventaire") int32 GetBackpackStartIndex() const { return GetHotbarSlotCount(); } UFUNCTION(BlueprintPure, Category = "Inventaire") int32 GetUsedSlotCount() const; UFUNCTION(BlueprintPure, Category = "Inventaire") const TArray& GetSlots() const { return Slots; } /** * Deplace la pile entiere d'un slot vers un autre. * - cible vide -> la pile entiere part * - meme objet -> fusion jusqu'a MaxStackSize, le reste demeure a la source * - objet different -> echange des deux slots * @return true si quelque chose a change. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool MoveItem(int32 FromIndex, int32 ToIndex); /** * Deplace seulement une partie d'une pile. * * Un transfert partiel vers un slot occupe par un objet DIFFERENT est * refuse : il n'existe aucune facon sensee d'echanger la moitie d'une pile * contre autre chose. Seul un deplacement total peut provoquer un echange. * * @return true si quelque chose a change. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool MoveItemQuantity(int32 FromIndex, int32 ToIndex, int32 Quantity); // ------------------------------------------------------------------ // Transferts entre deux inventaires (coffres) // ------------------------------------------------------------------ /** * Deplace le contenu d'un slot vers un autre, dans le MEME inventaire ou * dans un autre. C'est desormais l'unique implementation du deplacement : * MoveItem() et MoveItemQuantity() ne sont que des appels avec From == To. * * Statique parce qu'elle n'appartient ni a la source ni a la destination -- * elle arbitre entre les deux, et doit diffuser OnInventoryChanged sur les * DEUX. Oublier la seconde diffusion laisse la grille d'en face afficher un * etat perime jusqu'au prochain evenement, ce qui se lit comme un objet * qui disparait. * * @param Quantity nombre d'exemplaires ; MAX_int32 pour la pile entiere. * @return true si quelque chose a change. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") static bool TransferSlot(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To, int32 ToIndex, int32 Quantity); /** * Envoie le contenu d'un slot vers un autre inventaire sans designer de * case d'arrivee : c'est le clic droit de l'ecran de coffre. * * Passe par AddItem(), donc exactement comme un ramassage -- piles entamees * d'abord, puis barre rapide, puis grille. Ce qui ne rentre pas reste sur * place : un transfert rapide ne perd jamais rien. * * @return le nombre d'exemplaires reellement partis. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") static int32 QuickTransferSlot(UInventoryComponent* From, int32 FromIndex, UInventoryComponent* To); /** * Deverse tout ce qui peut entrer vers un autre inventaire : les boutons * "Tout ranger" et "Tout prendre". * * @param FirstIndex premier slot considere. Le joueur passe * GetBackpackStartIndex() pour que "Tout ranger" laisse sa barre * rapide tranquille -- ranger ses outils dans le coffre a chaque * retour d'expedition serait une punition, pas un confort. * @return le nombre d'exemplaires reellement partis. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") int32 TransferAllTo(UInventoryComponent* To, int32 FirstIndex = 0); /** * Transforme ce composant en contenant du monde : NewSlotCount cases de * grille et AUCUNE barre rapide, pour que GetBackpackStartIndex() vaille 0 * et que la grille affiche la totalite du coffre. * * A appeler avant que le composant contienne quoi que ce soit -- typiquement * depuis PreInitializeComponents() de l'acteur porteur. Reduire la taille * d'un conteneur deja rempli fait disparaitre ce qui depasse. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") void ConfigureAsContainer(int32 NewSlotCount); /** Index du premier slot libre, ou INDEX_NONE si l'inventaire est plein. */ UFUNCTION(BlueprintPure, Category = "Inventaire") int32 FindFirstEmptySlot() const; /** * Retire jusqu'a Quantity exemplaires d'un slot precis et renvoie ce qui a * ete retire. Sert au drop au sol et, plus tard, au transfert vers un coffre. * * Different de RemoveItem() qui pioche par type sans se soucier du slot : * ici le joueur a designe une case, on ne touche qu'a celle-la. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool TakeFromSlot(int32 SlotIndex, int32 Quantity, FInventorySlot& OutTaken); /** * Change le bonus de slots apporte par l'equipement (sac). * @return false si reduire la taille laisserait des objets dehors ; * dans ce cas rien n'est modifie et le sac ne doit pas etre retire. */ UFUNCTION(BlueprintCallable, Category = "Inventaire") bool SetBonusSlotCount(int32 NewBonusSlots); UPROPERTY(BlueprintAssignable, Category = "Inventaire") FOnInventoryChanged OnInventoryChanged; // ------------------------------------------------------------------ // Barre rapide // ------------------------------------------------------------------ // // La barre rapide n'est PAS un jeu de slots separe : ce sont les // HotbarSlotCount premiers slots de l'inventaire, simplement affiches // deux fois. Consequence directe : le glisser-deposer entre la barre et // la grille fonctionne sans une ligne de code de plus, puisque ce sont // les memes index dans le meme tableau. /** Change le slot actif. Un index hors de la barre est ignore. */ UFUNCTION(BlueprintCallable, Category = "Inventaire|Barre rapide") void SelectHotbarSlot(int32 Index); /** Avance ou recule dans la barre, en boucle. */ UFUNCTION(BlueprintCallable, Category = "Inventaire|Barre rapide") void CycleHotbarSelection(int32 Delta); UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide") int32 GetSelectedHotbarIndex() const { return SelectedHotbarIndex; } UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide") int32 GetHotbarSlotCount() const { return FMath::Min(HotbarSlotCount, Slots.Num()); } /** Contenu du slot actif. C'est ce que le joueur "a en main". */ UFUNCTION(BlueprintPure, Category = "Inventaire|Barre rapide") FInventorySlot GetSelectedSlot() const; UPROPERTY(BlueprintAssignable, Category = "Inventaire|Barre rapide") FOnSelectedHotbarSlotChanged OnSelectedHotbarSlotChanged; protected: /** Cases affichees dans la grille d'inventaire. N'inclut PAS la barre rapide. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire", meta = (ClampMin = "0")) int32 BackpackSlotCount = 20; /** Slots supplementaires apportes par l'equipement (sac). Passe par SetBonusSlotCount(). */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire") int32 BonusSlotCount = 0; /** * Cases de la barre rapide, independantes de celles de la grille. * Elles occupent les premiers index du tableau. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Inventaire|Barre rapide", meta = (ClampMin = "0")) int32 HotbarSlotCount = 10; /** Toujours dimensionne a GetTotalSlotCount(). */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Inventaire") TArray Slots; private: /** Redimensionne le tableau si la taille cible a change. */ void EnsureSlotCount(); /** Regroupe les slots occupes au debut du tableau. */ void CompactSlots(); /** * Regles de deplacement entre deux cases, sans savoir a quel inventaire * chacune appartient. Ne diffuse rien : c'est l'appelant qui sait combien * de composants ont bouge. */ static bool ApplySlotMove(FInventorySlot& From, FInventorySlot& To, int32 Quantity); /** * Verse un slot dans un autre inventaire via AddItem(). Ne diffuse que du * cote destination (AddItem s'en charge) : la source reste a la charge de * l'appelant, qui peut en enchainer vingt. * @return exemplaires reellement partis. */ int32 PushSlotInto(int32 SlotIndex, UInventoryComponent& To); /** * Diffusion differee. Un "Tout ranger" deplace jusqu'a vingt piles : sans * regroupement, l'UI se reconstruirait vingt fois pour un seul clic. * Toutes les modifications passent par BroadcastChanged() plutot que par * OnInventoryChanged.Broadcast() directement, sinon le lot fuit. */ void BroadcastChanged(); void BeginBatch(); void EndBatch(); int32 SelectedHotbarIndex = 0; int32 BatchDepth = 0; bool bBatchDirty = false; };