ed7c7fd991
Boucle de jeu complète : récolte, inventaire, consommation, stats de survie, mort et respawn. Gameplay en C++, Blueprints réservés au câblage d'assets. Assets binaires (.uasset, .umap, textures, audio) suivis via Git LFS. Binaries/, Intermediate/, Saved/ et DerivedDataCache/ sont ignorés : régénérés au build, ils pèsent 3 Go pour 1,3 Go de contenu utile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
240 lines
7.6 KiB
C++
240 lines
7.6 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
#include "InventorySlotWidget.h"
|
|
|
|
#include "Components/Image.h"
|
|
#include "Components/ProgressBar.h"
|
|
#include "Components/TextBlock.h"
|
|
#include "Components/Widget.h"
|
|
#include "InventoryDragDropOperation.h"
|
|
#include "InventoryWidget.h"
|
|
#include "ItemDataAsset.h"
|
|
|
|
void UInventorySlotWidget::NativeConstruct()
|
|
{
|
|
Super::NativeConstruct();
|
|
|
|
// Force en C++ plutot que de dependre d'un reglage du Blueprint : une case
|
|
// non testable a la souris ne recevrait jamais NativeOnMouseButtonDown, et
|
|
// le glisser-deposer serait mysterieusement inerte.
|
|
// NativeConstruct s'execute APRES ConfigureAsDragVisual() pour un fantome
|
|
// (le widget n'est construit qu'une fois ajoute a la couche de drag),
|
|
// d'ou le test : sans lui, on remettrait le fantome en Visible et il
|
|
// intercepterait le depot destine a la case du dessous.
|
|
SetVisibility(bIsDragVisual ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Visible);
|
|
}
|
|
|
|
void UInventorySlotWidget::SetSelected(bool bSelected)
|
|
{
|
|
if (SelectionBorder)
|
|
{
|
|
SelectionBorder->SetVisibility(bSelected ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
|
|
}
|
|
}
|
|
|
|
void UInventorySlotWidget::ConfigureAsDragVisual()
|
|
{
|
|
bIsDragVisual = true;
|
|
|
|
// Le cadre de selection appartient a la barre, pas a l'objet.
|
|
if (SelectionBorder)
|
|
{
|
|
SelectionBorder->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
// Le fond de case appartient a la grille, pas a l'objet. Le trainer sous
|
|
// le curseur donne l'impression de deplacer la case elle-meme.
|
|
if (SlotBackground)
|
|
{
|
|
SlotBackground->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
|
|
void UInventorySlotWidget::InitSlot(UInventoryComponent* InInventory, int32 InSlotIndex, UInventoryWidget* InOwningGrid)
|
|
{
|
|
OwningInventory = InInventory;
|
|
SlotIndex = InSlotIndex;
|
|
OwningGrid = InOwningGrid;
|
|
}
|
|
|
|
void UInventorySlotWidget::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
Super::NativeOnMouseEnter(InGeometry, InMouseEvent);
|
|
|
|
if (OwningGrid.IsValid())
|
|
{
|
|
OwningGrid->NotifySlotHovered(SlotIndex, true);
|
|
}
|
|
}
|
|
|
|
void UInventorySlotWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
|
|
{
|
|
Super::NativeOnMouseLeave(InMouseEvent);
|
|
|
|
if (OwningGrid.IsValid())
|
|
{
|
|
OwningGrid->NotifySlotHovered(SlotIndex, false);
|
|
}
|
|
}
|
|
|
|
void UInventorySlotWidget::SetSlot(const FInventorySlot& InSlot)
|
|
{
|
|
CachedSlot = InSlot;
|
|
|
|
// Filet de securite : toute mise a jour de contenu remet la case en pleine
|
|
// opacite, meme si un glisser s'est termine par un chemin imprevu.
|
|
if (!bIsDragVisual)
|
|
{
|
|
SetRenderOpacity(1.f);
|
|
}
|
|
|
|
const bool bEmpty = InSlot.IsEmpty();
|
|
|
|
if (ItemIcon)
|
|
{
|
|
// Hidden et non Collapsed : on veut que la case garde sa taille dans
|
|
// la grille meme vide, sinon la mise en page sauterait a chaque
|
|
// ramassage.
|
|
if (bEmpty || !InSlot.Item->Icon)
|
|
{
|
|
ItemIcon->SetVisibility(ESlateVisibility::Hidden);
|
|
}
|
|
else
|
|
{
|
|
ItemIcon->SetBrushFromTexture(InSlot.Item->Icon, /*bMatchSize=*/false);
|
|
ItemIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
}
|
|
|
|
if (QuantityText)
|
|
{
|
|
// On n'affiche pas "x1" : ca alourdit la grille pour rien.
|
|
if (bEmpty || InSlot.Quantity <= 1)
|
|
{
|
|
QuantityText->SetVisibility(ESlateVisibility::Hidden);
|
|
}
|
|
else
|
|
{
|
|
QuantityText->SetText(FText::AsNumber(InSlot.Quantity));
|
|
QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
}
|
|
|
|
if (UsesBar)
|
|
{
|
|
// Collapsed et non Hidden : une barre d'usure sur un objet qui n'en a
|
|
// pas ne doit meme pas reserver sa place au bas de la case.
|
|
const int32 MaxUses = bEmpty ? 1 : FMath::Max(1, InSlot.Item->MaxUses);
|
|
if (MaxUses <= 1)
|
|
{
|
|
UsesBar->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
else
|
|
{
|
|
UsesBar->SetPercent(FMath::Clamp(static_cast<float>(InSlot.RemainingUses) / static_cast<float>(MaxUses), 0.f, 1.f));
|
|
UsesBar->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply UInventorySlotWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
|
|
{
|
|
if (CachedSlot.IsEmpty() || !InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
|
|
{
|
|
return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
|
|
}
|
|
|
|
// On ne demarre pas le glisser tout de suite : DetectDrag attend que la
|
|
// souris bouge reellement. Sans ca, un simple clic serait interprete comme
|
|
// un debut de glisser et on ne pourrait jamais faire autre chose.
|
|
return FReply::Handled().DetectDrag(TakeWidget(), EKeys::LeftMouseButton);
|
|
}
|
|
|
|
void UInventorySlotWidget::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
|
|
{
|
|
Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);
|
|
|
|
if (CachedSlot.IsEmpty() || !OwningInventory.IsValid() || SlotIndex == INDEX_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Modificateurs au moment de la saisie : Maj pour la moitie, Ctrl pour un
|
|
// seul. Conventions etablies du genre, le joueur les connait deja.
|
|
int32 DraggedQuantity = CachedSlot.Quantity;
|
|
if (InMouseEvent.IsControlDown())
|
|
{
|
|
DraggedQuantity = 1;
|
|
}
|
|
else if (InMouseEvent.IsShiftDown())
|
|
{
|
|
// Moitie arrondie au superieur : sur une pile de 7 on emporte 4.
|
|
// C'est la convention de Minecraft et consorts.
|
|
DraggedQuantity = (CachedSlot.Quantity + 1) / 2;
|
|
}
|
|
|
|
UInventoryDragDropOperation* Operation = NewObject<UInventoryDragDropOperation>();
|
|
Operation->SourceSlotIndex = SlotIndex;
|
|
Operation->Quantity = DraggedQuantity;
|
|
Operation->SourceInventory = OwningInventory;
|
|
Operation->Pivot = EDragPivot::CenterCenter;
|
|
|
|
// Le fantome sous le curseur est une copie de cette meme case : aucun
|
|
// widget supplementaire a maintenir, et l'apparence reste coherente.
|
|
// Il affiche la quantite EMPORTEE, pas la pile complete.
|
|
if (UInventorySlotWidget* Visual = CreateWidget<UInventorySlotWidget>(GetOwningPlayer(), GetClass()))
|
|
{
|
|
FInventorySlot VisualSlot = CachedSlot;
|
|
VisualSlot.Quantity = DraggedQuantity;
|
|
|
|
Visual->SetSlot(VisualSlot);
|
|
Visual->ConfigureAsDragVisual();
|
|
Visual->SetRenderOpacity(DragVisualOpacity);
|
|
Operation->DefaultDragVisual = Visual;
|
|
}
|
|
|
|
// On n'estompe la case source que si la pile part en entier. Lors d'une
|
|
// division une partie reste sur place, et la faire palir serait mentir
|
|
// sur ce qu'elle contient encore.
|
|
if (DraggedQuantity >= CachedSlot.Quantity)
|
|
{
|
|
SetRenderOpacity(DraggedSlotOpacity);
|
|
}
|
|
|
|
// Les deux issues possibles remettent l'opacite : depot reussi, ou
|
|
// abandon hors de la grille.
|
|
Operation->OnDrop.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);
|
|
Operation->OnDragCancelled.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);
|
|
|
|
OutOperation = Operation;
|
|
}
|
|
|
|
void UInventorySlotWidget::HandleDragFinished(UDragDropOperation* Operation)
|
|
{
|
|
SetRenderOpacity(1.f);
|
|
}
|
|
|
|
bool UInventorySlotWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
|
|
{
|
|
const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
|
|
if (!Payload || SlotIndex == INDEX_NONE)
|
|
{
|
|
return Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
|
|
}
|
|
|
|
// On refuse les transferts entre deux inventaires differents : ce sera le
|
|
// jour ou tu ajouteras les coffres, avec ses propres regles.
|
|
if (!Payload->SourceInventory.IsValid() || Payload->SourceInventory != OwningInventory)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// MoveItemQuantity diffuse OnInventoryChanged, donc la grille se rafraichit
|
|
// seule. Une quantite egale a la pile entiere retombe sur le comportement
|
|
// complet, echange compris.
|
|
return OwningInventory->MoveItemQuantity(Payload->SourceSlotIndex, SlotIndex, Payload->Quantity);
|
|
}
|