// Fill out your copyright notice in the Description page of Project Settings. #include "InventoryComponent.h" #include "ItemDataAsset.h" UInventoryComponent::UInventoryComponent() { PrimaryComponentTick.bCanEverTick = false; // Necessaire pour que InitializeComponent() soit appele. bWantsInitializeComponent = true; } void UInventoryComponent::InitializeComponent() { Super::InitializeComponent(); EnsureSlotCount(); SelectedHotbarIndex = FMath::Clamp(SelectedHotbarIndex, 0, FMath::Max(0, GetHotbarSlotCount() - 1)); } void UInventoryComponent::SelectHotbarSlot(int32 Index) { const int32 Count = GetHotbarSlotCount(); if (Index < 0 || Index >= Count || Index == SelectedHotbarIndex) { return; } SelectedHotbarIndex = Index; OnSelectedHotbarSlotChanged.Broadcast(SelectedHotbarIndex); } void UInventoryComponent::CycleHotbarSelection(int32 Delta) { const int32 Count = GetHotbarSlotCount(); if (Count <= 0 || Delta == 0) { return; } // Modulo positif : en C++, -1 % 6 vaut -1 et non 5. Sans le double // modulo, reculer depuis le premier slot donnerait un index negatif. const int32 NewIndex = ((SelectedHotbarIndex + Delta) % Count + Count) % Count; SelectHotbarSlot(NewIndex); } FInventorySlot UInventoryComponent::GetSelectedSlot() const { return Slots.IsValidIndex(SelectedHotbarIndex) ? Slots[SelectedHotbarIndex] : FInventorySlot(); } void UInventoryComponent::EnsureSlotCount() { const int32 Target = GetTotalSlotCount(); if (Slots.Num() != Target) { Slots.SetNum(Target); } } int32 UInventoryComponent::AddItem(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses) { if (!Item || Quantity <= 0) { return Quantity; } EnsureSlotCount(); const int32 MaxStack = Item->GetEffectiveMaxStack(); const int32 MaxUses = FMath::Max(1, Item->MaxUses); // -1 = "plein" : un objet neuf, ou un ajout qui ne precise pas d'etat. const int32 StartingUses = (RemainingUses < 0) ? MaxUses : FMath::Clamp(RemainingUses, 0, MaxUses); int32 Remaining = Quantity; // Premiere passe : completer les piles deja entamees. On le fait avant // d'ouvrir un nouveau slot, sinon on gaspillerait des emplacements en // laissant des piles a moitie vides un peu partout. for (FInventorySlot& Slot : Slots) { if (Remaining <= 0) { break; } if (Slot.Item == Item && Slot.Quantity < MaxStack) { const int32 Added = FMath::Min(Remaining, MaxStack - Slot.Quantity); Slot.Quantity += Added; Remaining -= Added; } } // Deuxieme passe : ouvrir de nouveaux slots. La BARRE RAPIDE est servie en // premier, pour qu'un objet ramasse soit immediatement utilisable sans // passer par l'inventaire. auto FillEmptyRange = [&](int32 First, int32 Last) { for (int32 Index = First; Index < Last && Remaining > 0; ++Index) { FInventorySlot& Slot = Slots[Index]; if (Slot.IsEmpty()) { const int32 Added = FMath::Min(Remaining, MaxStack); Slot.Item = Item; Slot.Quantity = Added; Slot.RemainingUses = StartingUses; Remaining -= Added; } } }; // Inverse ces deux lignes si tu veux un jour servir l'inventaire d'abord. const int32 BackpackStart = GetBackpackStartIndex(); FillEmptyRange(0, BackpackStart); // Barre pleine : on deborde dans l'inventaire. FillEmptyRange(BackpackStart, Slots.Num()); if (Remaining < Quantity) { OnInventoryChanged.Broadcast(); } return Remaining; } int32 UInventoryComponent::RemoveItem(UItemDataAsset* Item, int32 Quantity) { if (!Item || Quantity <= 0) { return 0; } int32 Remaining = Quantity; // On vide les plus petites piles d'abord : ca libere des slots au plus // vite au lieu de laisser trainer plusieurs piles a moitie pleines. while (Remaining > 0) { FInventorySlot* Best = nullptr; for (FInventorySlot& Slot : Slots) { if (Slot.Item == Item && Slot.Quantity > 0) { if (!Best || Slot.Quantity < Best->Quantity) { Best = &Slot; } } } if (!Best) { break; } const int32 Taken = FMath::Min(Remaining, Best->Quantity); Best->Quantity -= Taken; Remaining -= Taken; if (Best->Quantity <= 0) { Best->Clear(); } } const int32 Removed = Quantity - Remaining; if (Removed > 0) { OnInventoryChanged.Broadcast(); } return Removed; } bool UInventoryComponent::ConsumeUse(int32 SlotIndex) { if (!Slots.IsValidIndex(SlotIndex)) { return false; } FInventorySlot& Slot = Slots[SlotIndex]; if (Slot.IsEmpty()) { return false; } const int32 MaxUses = FMath::Max(1, Slot.Item->MaxUses); // Objet a usage unique : un exemplaire disparait, point. if (MaxUses <= 1) { Slot.Quantity -= 1; if (Slot.Quantity <= 0) { Slot.Clear(); } OnInventoryChanged.Broadcast(); return true; } Slot.RemainingUses -= 1; if (Slot.RemainingUses <= 0) { Slot.Quantity -= 1; if (Slot.Quantity <= 0) { Slot.Clear(); } else { // Cas theorique : un objet a charges ne s'empile pas. On recharge // quand meme l'exemplaire suivant, au cas ou une pile serait // construite autrement un jour. Slot.RemainingUses = MaxUses; } } OnInventoryChanged.Broadcast(); return true; } int32 UInventoryComponent::GetItemCount(const UItemDataAsset* Item) const { if (!Item) { return 0; } int32 Total = 0; for (const FInventorySlot& Slot : Slots) { if (Slot.Item == Item) { Total += Slot.Quantity; } } return Total; } int32 UInventoryComponent::GetRoomFor(const UItemDataAsset* Item) const { if (!Item) { return 0; } const int32 MaxStack = Item->GetEffectiveMaxStack(); int32 Room = 0; for (const FInventorySlot& Slot : Slots) { if (Slot.IsEmpty()) { Room += MaxStack; } else if (Slot.Item == Item) { Room += FMath::Max(0, MaxStack - Slot.Quantity); } } return Room; } int32 UInventoryComponent::GetUsedSlotCount() const { int32 Used = 0; for (const FInventorySlot& Slot : Slots) { if (!Slot.IsEmpty()) { ++Used; } } return Used; } int32 UInventoryComponent::FindFirstEmptySlot() const { for (int32 Index = 0; Index < Slots.Num(); ++Index) { if (Slots[Index].IsEmpty()) { return Index; } } return INDEX_NONE; } bool UInventoryComponent::TakeFromSlot(int32 SlotIndex, int32 Quantity, FInventorySlot& OutTaken) { OutTaken.Clear(); if (Quantity <= 0 || !Slots.IsValidIndex(SlotIndex)) { return false; } FInventorySlot& Slot = Slots[SlotIndex]; if (Slot.IsEmpty()) { return false; } const int32 Taken = FMath::Min(Quantity, Slot.Quantity); OutTaken.Item = Slot.Item; OutTaken.Quantity = Taken; // L'etat d'usure part avec l'objet : jeter une canette entamee puis la // reprendre ne doit pas la remplir. OutTaken.RemainingUses = Slot.RemainingUses; Slot.Quantity -= Taken; if (Slot.Quantity <= 0) { Slot.Clear(); } OnInventoryChanged.Broadcast(); return true; } bool UInventoryComponent::MoveItemQuantity(int32 FromIndex, int32 ToIndex, int32 Quantity) { if (Quantity <= 0 || FromIndex == ToIndex || !Slots.IsValidIndex(FromIndex) || !Slots.IsValidIndex(ToIndex)) { return false; } FInventorySlot& From = Slots[FromIndex]; if (From.IsEmpty()) { return false; } // Deplacement total : on retombe sur le comportement complet, echange compris. if (Quantity >= From.Quantity) { return MoveItem(FromIndex, ToIndex); } FInventorySlot& To = Slots[ToIndex]; const int32 MaxStack = From.Item->GetEffectiveMaxStack(); if (To.IsEmpty()) { const int32 Transferred = FMath::Min(Quantity, MaxStack); To.Item = From.Item; To.Quantity = Transferred; To.RemainingUses = From.RemainingUses; From.Quantity -= Transferred; OnInventoryChanged.Broadcast(); return true; } if (To.Item == From.Item) { const int32 Transferable = FMath::Min(Quantity, MaxStack - To.Quantity); if (Transferable <= 0) { return false; } To.Quantity += Transferable; From.Quantity -= Transferable; OnInventoryChanged.Broadcast(); return true; } // Objet different et transfert partiel : aucune interpretation raisonnable. // On refuse plutot que d'inventer un comportement que le joueur ne // comprendrait pas. return false; } bool UInventoryComponent::MoveItem(int32 FromIndex, int32 ToIndex) { if (FromIndex == ToIndex || !Slots.IsValidIndex(FromIndex) || !Slots.IsValidIndex(ToIndex)) { return false; } FInventorySlot& From = Slots[FromIndex]; if (From.IsEmpty()) { return false; } FInventorySlot& To = Slots[ToIndex]; // Cible vide : la pile entiere part, quelle que soit sa taille. if (To.IsEmpty()) { To = From; From.Clear(); OnInventoryChanged.Broadcast(); return true; } // Meme objet : on fusionne dans la limite de la pile. Pour un objet a // charges, GetEffectiveMaxStack vaut 1, donc on tombe toujours sur // l'echange -- deux canettes entamees ne fusionnent jamais. if (To.Item == From.Item) { const int32 MaxStack = From.Item->GetEffectiveMaxStack(); const int32 Transferable = FMath::Min(From.Quantity, MaxStack - To.Quantity); // Cible deja pleine : on echange plutot que de ne rien faire, sinon le // geste du joueur resterait sans effet et passerait pour un bug. if (Transferable <= 0) { Swap(From, To); OnInventoryChanged.Broadcast(); return true; } To.Quantity += Transferable; From.Quantity -= Transferable; if (From.Quantity <= 0) { From.Clear(); } OnInventoryChanged.Broadcast(); return true; } // Objets differents : simple echange. Swap(From, To); OnInventoryChanged.Broadcast(); return true; } bool UInventoryComponent::SetBonusSlotCount(int32 NewBonusSlots) { const int32 NewTotal = FMath::Max(0, HotbarSlotCount + BackpackSlotCount + FMath::Max(0, NewBonusSlots)); // On refuse plutot que de detruire du butin en silence. C'est a l'appelant // (le code qui deséquipe le sac) de decider quoi faire : bloquer le retrait, // ou vider le surplus au sol avant de reessayer. if (GetUsedSlotCount() > NewTotal) { return false; } BonusSlotCount = FMath::Max(0, NewBonusSlots); // Compacter avant de tronquer : sans ca, un objet range dans un slot de // fin serait supprime alors qu'il reste de la place au debut. CompactSlots(); Slots.SetNum(NewTotal); OnInventoryChanged.Broadcast(); return true; } void UInventoryComponent::CompactSlots() { const int32 Start = GetBackpackStartIndex(); const int32 Count = Slots.Num() - Start; if (Count <= 0) { return; } // On ne compacte QUE la portion sac. Compacter tout le tableau ferait // remonter des objets du sac dans la barre rapide sans que le joueur // l'ait demande -- et la troncature ne retire de toute facon que la fin. TArray Backpack; Backpack.Append(Slots.GetData() + Start, Count); Backpack.StableSort([](const FInventorySlot& A, const FInventorySlot& B) { return !A.IsEmpty() && B.IsEmpty(); }); for (int32 Index = 0; Index < Count; ++Index) { Slots[Start + Index] = Backpack[Index]; } }