|
// InventoryComponent.cpp |
|
|
|
#include "InventoryComponent.h" |
|
|
|
UInventoryComponent::UInventoryComponent() |
|
{ |
|
PrimaryComponentTick.bCanEverTick = false; |
|
} |
|
|
|
void UInventoryComponent::BeginPlay() |
|
{ |
|
Super::BeginPlay(); |
|
} |
|
|
|
FInventoryItem UInventoryComponent::GetItemAt(int32 Index) const |
|
{ |
|
if (!IsValidIndex(Index)) return FInventoryItem{}; |
|
if (const FInventoryItem* Found = Slots.Find(Index)) |
|
{ |
|
return *Found; |
|
} |
|
return FInventoryItem{}; |
|
} |
|
|
|
void UInventoryComponent::BroadcastChange(int32 Index) const |
|
{ |
|
const FInventoryItem Current = const_cast<UInventoryComponent*>(this)->GetItemAt(Index); |
|
if (OnSlotChanged.IsBound()) |
|
{ |
|
OnSlotChanged.Broadcast(Index, Current); |
|
} |
|
} |
|
|
|
void UInventoryComponent::ClearSlot(int32 Index) |
|
{ |
|
Slots.Remove(Index); |
|
BroadcastChange(Index); |
|
} |
|
|
|
bool UInventoryComponent::SetItemAt(int32 Index, const FInventoryItem& Item) |
|
{ |
|
if (!IsValidIndex(Index)) return false; |
|
|
|
if (Item.IsEmpty()) |
|
{ |
|
if (Slots.Contains(Index)) |
|
{ |
|
ClearSlot(Index); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
Slots.Add(Index, Item); |
|
BroadcastChange(Index); |
|
return true; |
|
} |
|
|
|
FInventoryItem UInventoryComponent::RemoveAt(int32 Index) |
|
{ |
|
if (!IsValidIndex(Index)) return FInventoryItem{}; |
|
if (FInventoryItem* Found = Slots.Find(Index)) |
|
{ |
|
FInventoryItem Out = *Found; |
|
Slots.Remove(Index); |
|
BroadcastChange(Index); |
|
return Out; |
|
} |
|
return FInventoryItem{}; |
|
} |
|
|
|
int32 UInventoryComponent::FindFirstEmptySlot() const |
|
{ |
|
for (int32 i = 0; i < Capacity; ++i) |
|
{ |
|
if (!Slots.Contains(i)) return i; |
|
} |
|
return -1; |
|
} |
|
|
|
int32 UInventoryComponent::MergeSlots(int32 From, int32 To) |
|
{ |
|
if (!IsValidIndex(From) || !IsValidIndex(To) || From == To) return 0; |
|
|
|
FInventoryItem* A = Slots.Find(From); |
|
FInventoryItem* B = Slots.Find(To); |
|
if (!A || !B) return 0; |
|
if (A->IsEmpty() || B->IsEmpty()) return 0; |
|
if (A->Id != B->Id) return 0; |
|
|
|
const int32 Space = FMath::Max(0, B->MaxStack - B->Quantity); |
|
if (Space <= 0) return 0; |
|
|
|
const int32 Move = FMath::Min(Space, A->Quantity); |
|
B->Quantity += Move; |
|
A->Quantity -= Move; |
|
|
|
// Clean up the source if emptied |
|
if (A->Quantity <= 0) |
|
{ |
|
ClearSlot(From); |
|
} |
|
else |
|
{ |
|
BroadcastChange(From); |
|
} |
|
BroadcastChange(To); |
|
return Move; |
|
} |
|
|
|
bool UInventoryComponent::MoveItem(int32 From, int32 To, bool bMergeIfSame) |
|
{ |
|
if (!IsValidIndex(From) || !IsValidIndex(To) || From == To) return false; |
|
|
|
FInventoryItem FromItem = GetItemAt(From); |
|
if (FromItem.IsEmpty()) return false; |
|
|
|
FInventoryItem ToItem = GetItemAt(To); |
|
|
|
// Merge path |
|
if (bMergeIfSame && !ToItem.IsEmpty() && ToItem.Id == FromItem.Id) |
|
{ |
|
const int32 Space = FMath::Max(0, ToItem.MaxStack - ToItem.Quantity); |
|
if (Space > 0) |
|
{ |
|
const int32 Move = FMath::Min(Space, FromItem.Quantity); |
|
ToItem.Quantity += Move; |
|
FromItem.Quantity -= Move; |
|
|
|
// Apply |
|
if (FromItem.Quantity <= 0) ClearSlot(From); |
|
else SetItemAt(From, FromItem); |
|
|
|
SetItemAt(To, ToItem); |
|
return Move > 0; |
|
} |
|
// No space to merge; fall through to swap if allowed by caller via SwapItems. |
|
} |
|
|
|
// Simple move: overwrite destination and clear source |
|
SetItemAt(To, FromItem); |
|
ClearSlot(From); |
|
return true; |
|
} |
|
|
|
bool UInventoryComponent::SwapItems(int32 A, int32 B) |
|
{ |
|
if (!IsValidIndex(A) || !IsValidIndex(B) || A == B) return false; |
|
|
|
const bool bHasA = Slots.Contains(A); |
|
const bool bHasB = Slots.Contains(B); |
|
|
|
// Nothing to swap |
|
if (!bHasA && !bHasB) return false; |
|
|
|
if (bHasA && bHasB) |
|
{ |
|
// Swap the values in place |
|
FInventoryItem* ItemA = Slots.Find(A); |
|
FInventoryItem* ItemB = Slots.Find(B); |
|
check(ItemA && ItemB); |
|
Swap(*ItemA, *ItemB); |
|
BroadcastChange(A); |
|
BroadcastChange(B); |
|
return true; |
|
} |
|
|
|
// Exactly one has an item: move it to the other and clear source |
|
if (bHasA) // move A -> B |
|
{ |
|
const FInventoryItem Temp = *Slots.Find(A); |
|
SetItemAt(B, Temp); |
|
ClearSlot(A); |
|
return true; |
|
} |
|
else // bHasB: move B -> A |
|
{ |
|
const FInventoryItem Temp = *Slots.Find(B); |
|
SetItemAt(A, Temp); |
|
ClearSlot(B); |
|
return true; |
|
} |
|
} |
|
|
|
FInventoryItem UInventoryComponent::AddItem(FInventoryItem Item) |
|
{ |
|
if (Item.IsEmpty()) return FInventoryItem{}; |
|
|
|
// 1) Merge into existing stacks (same Id) |
|
for (int32 i = 0; i < Capacity && Item.Quantity > 0; ++i) |
|
{ |
|
if (FInventoryItem* Slot = Slots.Find(i)) |
|
{ |
|
if (!Slot->IsEmpty() && Slot->Id == Item.Id && Slot->Quantity < Slot->MaxStack) |
|
{ |
|
const int32 Space = Slot->MaxStack - Slot->Quantity; |
|
const int32 Move = FMath::Min(Space, Item.Quantity); |
|
Slot->Quantity += Move; |
|
Item.Quantity -= Move; |
|
BroadcastChange(i); |
|
} |
|
} |
|
} |
|
|
|
// 2) Place in empty slots |
|
while (Item.Quantity > 0) |
|
{ |
|
const int32 Empty = FindFirstEmptySlot(); |
|
if (Empty == -1) break; |
|
|
|
const int32 ToPlace = FMath::Min(Item.MaxStack, Item.Quantity); |
|
FInventoryItem Chunk = Item; |
|
Chunk.Quantity = ToPlace; |
|
|
|
SetItemAt(Empty, Chunk); |
|
Item.Quantity -= ToPlace; |
|
} |
|
|
|
// If any quantity remains, it didn't fit (return remainder) |
|
return Item; |
|
} |