Files
Mathew 41d6e6a7f3 (Feat) Add Storage Chests + Item Details Panel
Coffres : AStorageContainer, un UInventoryComponent configure en conteneur,
affiche comme panneau de WBP_InventoryScreen plutot que dans un ecran a lui.
Deplacement unifie par UInventoryComponent::TransferSlot / TransferAllTo.

Panneau de detail : UItemDetailsWidget, pose DANS WBP_Inventory a droite de la
grille. Icone, nom, separation, description, barre d'usure. Il recoit un couple
(inventaire, index) et s'abonne a OnInventoryChanged, donc une charge consommee
sous le curseur se voit.

Une case ne connait plus sa grille : elle diffuse OnHoverChanged. La barre
rapide s'y abonne aussi et relaie par le PlayerController, seul a posseder a la
fois le HUD et l'ecran. Le panneau efface la case qu'on QUITTE et non lui-meme,
sinon passer de la barre a la grille viderait ce qui vient d'etre affiche.

L'ecran allume et eteint le panneau (SetItemDetailsEnabled) : WBP_Inventory
etant instancie deux fois en mode coffre, une case a cocher par instance serait
un bug qui attend qu'on oublie de la decocher.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:51:24 +02:00

305 lines
8.5 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "InventoryScreenWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Components/WidgetSwitcher.h"
#include "CraftingWidget.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InventoryComponent.h"
#include "InventoryDragDropOperation.h"
#include "InventoryWidget.h"
#include "StorageContainer.h"
void UInventoryScreenWidget::NativeConstruct()
{
Super::NativeConstruct();
if (InventoryTabButton)
{
InventoryTabButton->OnClicked.AddDynamic(this, &UInventoryScreenWidget::HandleInventoryTabClicked);
}
if (CraftingTabButton)
{
CraftingTabButton->OnClicked.AddDynamic(this, &UInventoryScreenWidget::HandleCraftingTabClicked);
}
if (StoreAllButton)
{
StoreAllButton->OnClicked.AddDynamic(this, &UInventoryScreenWidget::HandleStoreAllClicked);
}
if (TakeAllButton)
{
TakeAllButton->OnClicked.AddDynamic(this, &UInventoryScreenWidget::HandleTakeAllClicked);
}
// La grille de coffre n'affiche jamais de detail : c'est le meme
// WBP_Inventory que le sac, elle en porte donc un exemplaire qu'on coupe une
// fois pour toutes ici plutot que de compter sur une case a cocher.
if (StorageGrid)
{
StorageGrid->SetItemDetailsEnabled(false);
}
// Le panneau de coffre ne se montre qu'a l'ouverture d'un coffre. On le
// masque en C++ plutot que de dependre de ce qui a ete regle dans la
// Designer : un panneau laisse Visible par erreur afficherait une grille
// vide en permanence a cote du sac.
HideStorage();
ShowInventoryTab();
}
void UInventoryScreenWidget::BindToOwningPawn()
{
if (InventoryPage)
{
InventoryPage->BindToOwningPawn();
}
if (CraftingPage)
{
CraftingPage->BindToOwningPawn();
}
}
bool UInventoryScreenWidget::IsInventoryTabActive() const
{
return TabSwitcher && InventoryPage && TabSwitcher->GetActiveWidget() == InventoryPage;
}
int32 UInventoryScreenWidget::GetHoveredSlotIndex() const
{
if (!InventoryPage || !IsInventoryTabActive())
{
return INDEX_NONE;
}
return InventoryPage->GetHoveredSlotIndex();
}
bool UInventoryScreenWidget::GetHoveredSlot(UInventoryComponent*& OutInventory, int32& OutIndex) const
{
// Le coffre en premier : c'est le panneau le plus a droite, donc celui que
// le curseur survole quand les deux sont affiches.
if (bStorageVisible && StorageGrid && StorageGrid->GetHoveredSlot(OutInventory, OutIndex))
{
return true;
}
if (InventoryPage && IsInventoryTabActive() && InventoryPage->GetHoveredSlot(OutInventory, OutIndex))
{
return true;
}
OutInventory = nullptr;
OutIndex = INDEX_NONE;
return false;
}
void UInventoryScreenWidget::NotifyExternalSlotHovered(UInventoryComponent* Inventory, int32 SlotIndex, bool bHovered)
{
// Rien a faire sous l'onglet Fabrication : le panneau est parti avec la
// page, et la barre rapide y est de toute facon masquee.
if (InventoryPage && IsInventoryTabActive())
{
InventoryPage->NotifyExternalSlotHovered(Inventory, SlotIndex, bHovered);
}
}
UInventoryComponent* UInventoryScreenWidget::GetPlayerInventory() const
{
const APlayerController* OwningController = GetOwningPlayer();
AFpsPlayer* PlayerPawn = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
return PlayerPawn ? PlayerPawn->GetInventoryComponent() : nullptr;
}
void UInventoryScreenWidget::ShowStorage(AStorageContainer* Container)
{
UInventoryComponent* Contents = Container ? Container->GetStorage() : nullptr;
if (!Contents)
{
UE_LOG(LogTemp, Warning, TEXT("UInventoryScreenWidget::ShowStorage : coffre nul ou sans inventaire."));
return;
}
if (!StoragePanel || !StorageGrid)
{
UE_LOG(LogTemp, Warning, TEXT("UInventoryScreenWidget : 'StoragePanel' ou 'StorageGrid' manque dans WBP_InventoryScreen, le coffre ne s'affichera pas."));
return;
}
BoundStorage = Contents;
bStorageVisible = true;
StorageGrid->SetColumnCount(Container->GetColumnCount());
StorageGrid->BindToInventory(Contents);
// Le clic droit envoie d'un cote a l'autre. Le pawn a pu changer depuis la
// derniere ouverture (respawn), d'ou la relecture plutot qu'une valeur
// gardee en cache.
StorageGrid->SetQuickTransferTarget(GetPlayerInventory());
if (InventoryPage)
{
InventoryPage->SetQuickTransferTarget(Contents);
// Le detail disparait en mode coffre : l'ecran passe deja a deux grilles
// cote a cote, une troisieme colonne le rendrait illisible.
InventoryPage->SetItemDetailsEnabled(false);
}
if (StorageTitle)
{
StorageTitle->SetText(Container->GetDisplayName());
}
StoragePanel->SetVisibility(ESlateVisibility::Visible);
// Un coffre ouvert sous l'onglet Fabrication n'aurait aucun sens : on force
// l'onglet Inventaire, et ShowPage grise le bouton Fabrication tant que
// bStorageVisible est vrai.
ShowInventoryTab();
}
void UInventoryScreenWidget::HideStorage()
{
BoundStorage.Reset();
bStorageVisible = false;
// On coupe les transferts rapides AVANT de debrancher : une case qui garde
// un coffre en memoire y enverrait encore des objets au prochain clic droit,
// panneau ferme.
if (InventoryPage)
{
InventoryPage->SetQuickTransferTarget(nullptr);
// Le sac retrouve son panneau. Repose a chaque fermeture, y compris a la
// construction : c'est l'etat par defaut de l'ecran, il ne doit dependre
// ni de la Designer ni de ce qu'a laisse la session precedente.
InventoryPage->SetItemDetailsEnabled(true);
}
if (StorageGrid)
{
StorageGrid->SetQuickTransferTarget(nullptr);
StorageGrid->BindToInventory(nullptr);
}
if (StoragePanel)
{
StoragePanel->SetVisibility(ESlateVisibility::Collapsed);
}
// Rend la main a l'onglet Fabrication, que ShowPage avait grise.
if (CraftingTabButton)
{
CraftingTabButton->SetIsEnabled(IsInventoryTabActive());
}
}
void UInventoryScreenWidget::HandleStoreAllClicked()
{
UInventoryComponent* PlayerInventory = GetPlayerInventory();
if (!PlayerInventory || !BoundStorage.IsValid())
{
return;
}
// A partir de la grille seulement : la barre rapide porte les outils qu'on
// vient d'utiliser, et les ranger a chaque retour d'expedition serait une
// punition deguisee en confort.
PlayerInventory->TransferAllTo(BoundStorage.Get(), PlayerInventory->GetBackpackStartIndex());
}
void UInventoryScreenWidget::HandleTakeAllClicked()
{
UInventoryComponent* PlayerInventory = GetPlayerInventory();
if (!PlayerInventory || !BoundStorage.IsValid())
{
return;
}
// Depuis l'index 0 : un coffre n'a pas de barre rapide. Ce qui ne rentre
// pas dans le sac reste dans le coffre.
BoundStorage->TransferAllTo(PlayerInventory, 0);
}
void UInventoryScreenWidget::ShowInventoryTab()
{
ShowPage(InventoryPage);
}
void UInventoryScreenWidget::ShowCraftingTab()
{
ShowPage(CraftingPage);
}
void UInventoryScreenWidget::HandleInventoryTabClicked()
{
ShowPage(InventoryPage);
}
void UInventoryScreenWidget::HandleCraftingTabClicked()
{
ShowPage(CraftingPage);
}
void UInventoryScreenWidget::ShowPage(UWidget* Page)
{
if (!TabSwitcher || !Page)
{
return;
}
TabSwitcher->SetActiveWidget(Page);
// Le bouton de l'onglet courant est desactive : il sert d'indicateur de
// position autant que de garde-fou contre un clic sans effet.
const bool bInventoryActive = (Page == InventoryPage);
if (InventoryTabButton)
{
InventoryTabButton->SetIsEnabled(!bInventoryActive);
}
if (CraftingTabButton)
{
// Grise aussi tant qu'un coffre est ouvert : ses ressources ne comptent
// pas encore dans les recettes, et un clic dont le resultat serait
// incomprehensible vaut mieux etre rendu impossible.
CraftingTabButton->SetIsEnabled(bInventoryActive && !bStorageVisible);
}
OnTabChanged.Broadcast(bInventoryActive);
}
bool UInventoryScreenWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
// On n'arrive ici que si ni une case ni la page d'inventaire n'ont traite le
// depot : les enfants sont consultes en premier et arretent la propagation.
const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
if (!Payload || Payload->SourceSlotIndex == INDEX_NONE)
{
return true;
}
// Depuis l'inventaire SOURCE et non celui du joueur : sortir une pile du
// coffre en la lachant dans le vide doit la retirer du coffre.
if (const APlayerController* OwningController = GetOwningPlayer())
{
if (AFpsPlayer* PlayerPawn = Cast<AFpsPlayer>(OwningController->GetPawn()))
{
PlayerPawn->DropFromInventory(Payload->SourceInventory.Get(), Payload->SourceSlotIndex, Payload->Quantity);
}
}
return true;
}