// Fill out your copyright notice in the Description page of Project Settings. #include "InventoryWidget.h" #include "Components/UniformGridPanel.h" #include "Components/UniformGridSlot.h" #include "FpsPlayer.h" #include "GameFramework/PlayerController.h" #include "InventoryComponent.h" #include "InventoryDragDropOperation.h" #include "InventorySlotWidget.h" void UInventoryWidget::NativeConstruct() { Super::NativeConstruct(); BindToOwningPawn(); } void UInventoryWidget::BindToOwningPawn() { UInventoryComponent* NewInventory = ResolveInventory(); if (BoundInventory.Get() == NewInventory) { return; } if (BoundInventory.IsValid()) { BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged); } BoundInventory = NewInventory; if (BoundInventory.IsValid()) { BoundInventory->OnInventoryChanged.AddDynamic(this, &UInventoryWidget::HandleInventoryChanged); } else { UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : aucun UInventoryComponent trouve sur le pawn possede.")); } // Les cases gardent l'ancien composant en memoire : on force leur // recreation en vidant la grille. for (UInventorySlotWidget* SlotWidget : SlotWidgets) { if (SlotWidget) { SlotWidget->RemoveFromParent(); } } SlotWidgets.Reset(); Refresh(); } void UInventoryWidget::NativeDestruct() { if (BoundInventory.IsValid()) { BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UInventoryWidget::HandleInventoryChanged); } BoundInventory.Reset(); Super::NativeDestruct(); } UInventoryComponent* UInventoryWidget::ResolveInventory() const { const APlayerController* OwningController = GetOwningPlayer(); AFpsPlayer* Player = OwningController ? Cast(OwningController->GetPawn()) : nullptr; return Player ? Player->GetInventoryComponent() : nullptr; } void UInventoryWidget::HandleInventoryChanged() { Refresh(); } void UInventoryWidget::NotifySlotHovered(int32 Index, bool bHovered) { if (bHovered) { HoveredSlotIndex = Index; } else if (HoveredSlotIndex == Index) { // Le test d'egalite evite d'effacer le survol quand les evenements // arrivent dans l'ordre "entre dans B" puis "sort de A". HoveredSlotIndex = INDEX_NONE; } } bool UInventoryWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation) { Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation); // On n'arrive ici que si AUCUNE case n'a traite le depot : les widgets // enfants sont consultes en premier et arretent la propagation. const UInventoryDragDropOperation* Payload = Cast(InOperation); if (!Payload || Payload->SourceSlotIndex == INDEX_NONE) { return true; } // Lache DANS la grille mais entre deux cases (l'espacement) : on absorbe. // Jeter un objet au sol parce que le joueur a rate une case de trois // pixels serait impardonnable dans un jeu de survie. if (SlotGrid && SlotGrid->GetCachedGeometry().IsUnderLocation(InDragDropEvent.GetScreenSpacePosition())) { return true; } // Lache hors de la grille : on jette au sol, comme dans Valheim ou Raft. if (const APlayerController* OwningController = GetOwningPlayer()) { if (AFpsPlayer* Player = Cast(OwningController->GetPawn())) { Player->DropSlot(Payload->SourceSlotIndex, Payload->Quantity); } } return true; } void UInventoryWidget::Refresh() { if (!SlotGrid) { UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget::Refresh : SlotGrid est nul. Le widget nomme 'SlotGrid' manque dans le Blueprint.")); return; } if (!BoundInventory.IsValid()) { UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget::Refresh : aucun inventaire lie.")); return; } const TArray& Slots = BoundInventory->GetSlots(); // La grille commence APRES la barre rapide : les deux conteneurs sont // independants, un objet dans la barre n'apparait pas ici. const int32 Offset = BoundInventory->GetBackpackStartIndex(); const int32 GridCount = FMath::Max(0, Slots.Num() - Offset); // Le nombre de slots peut changer en cours de partie (sac equipe). // On ne reconstruit que si la taille a bouge : recreer toutes les cases // a chaque ramassage serait du gaspillage pur. RebuildGrid(GridCount); for (int32 Index = 0; Index < SlotWidgets.Num(); ++Index) { const int32 Absolute = Offset + Index; if (SlotWidgets[Index] && Slots.IsValidIndex(Absolute)) { SlotWidgets[Index]->SetSlot(Slots[Absolute]); } } } void UInventoryWidget::RebuildGrid(int32 DesiredCount) { if (SlotWidgets.Num() == DesiredCount) { return; } if (!SlotWidgetClass) { UE_LOG(LogTemp, Warning, TEXT("UInventoryWidget : SlotWidgetClass n'est pas assignee, la grille restera vide.")); return; } // Trop de cases : on retire le surplus par la fin. while (SlotWidgets.Num() > DesiredCount) { const int32 Last = SlotWidgets.Num() - 1; if (SlotWidgets[Last]) { SlotWidgets[Last]->RemoveFromParent(); } SlotWidgets.RemoveAt(Last); } // Pas assez : on en cree, en les placant en ligne / colonne. const int32 Columns = FMath::Max(1, ColumnCount); while (SlotWidgets.Num() < DesiredCount) { UInventorySlotWidget* NewSlot = CreateWidget(this, SlotWidgetClass); if (!NewSlot) { // Arrive typiquement quand le Widget Blueprint de case ne compile // plus, apres un changement de BindWidget cote C++ par exemple. UE_LOG(LogTemp, Error, TEXT("UInventoryWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."), *GetNameSafe(SlotWidgetClass)); break; } const int32 Index = SlotWidgets.Num(); // On donne l'index ABSOLU dans le tableau, pas la position dans la // grille. C'est ce qui rend le glisser entre la barre et la grille // gratuit : les deux manipulent les memes index. const int32 Absolute = BoundInventory->GetBackpackStartIndex() + Index; NewSlot->InitSlot(BoundInventory.Get(), Absolute, this); SlotGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns); SlotWidgets.Add(NewSlot); } }