Files
Unreal_EmberWild/Source/Survival_projet/Private/CraftingWidget.cpp
T
2026-07-30 23:08:33 +02:00

398 lines
9.4 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "CraftingWidget.h"
#include "Components/Button.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Components/UniformGridPanel.h"
#include "CraftingComponent.h"
#include "CraftingIngredientRowWidget.h"
#include "CraftingRecipeSlotWidget.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
void UCraftingWidget::NativeConstruct()
{
Super::NativeConstruct();
if (CraftButton)
{
CraftButton->OnClicked.AddDynamic(this, &UCraftingWidget::HandleCraftClicked);
}
BindToOwningPawn();
}
void UCraftingWidget::NativeDestruct()
{
UnbindDelegates();
Super::NativeDestruct();
}
UCraftingComponent* UCraftingWidget::ResolveCrafting() const
{
const APlayerController* OwningController = GetOwningPlayer();
AFpsPlayer* PlayerPawn = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
return PlayerPawn ? PlayerPawn->GetCraftingComponent() : nullptr;
}
void UCraftingWidget::UnbindDelegates()
{
if (BoundInventory.IsValid())
{
BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UCraftingWidget::HandleInventoryChanged);
}
BoundInventory.Reset();
if (BoundCrafting.IsValid())
{
BoundCrafting->OnAvailableStationsChanged.RemoveDynamic(this, &UCraftingWidget::HandleStationsChanged);
}
BoundCrafting.Reset();
}
void UCraftingWidget::BindToOwningPawn()
{
UCraftingComponent* NewCrafting = ResolveCrafting();
if (BoundCrafting.Get() == NewCrafting)
{
return;
}
UnbindDelegates();
BoundCrafting = NewCrafting;
if (BoundCrafting.IsValid())
{
BoundCrafting->OnAvailableStationsChanged.AddDynamic(this, &UCraftingWidget::HandleStationsChanged);
// L'inventaire est la vraie source des couleurs et de l'etat du bouton :
// c'est lui qui change quand le joueur ramasse ou consomme.
if (const AActor* Owner = BoundCrafting->GetOwner())
{
BoundInventory = Owner->FindComponentByClass<UInventoryComponent>();
if (BoundInventory.IsValid())
{
BoundInventory->OnInventoryChanged.AddDynamic(this, &UCraftingWidget::HandleInventoryChanged);
}
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : aucun UCraftingComponent trouve sur le pawn possede."));
}
// Les cases gardent l'ancienne recette et l'ancien parent en memoire : on
// force leur recreation en vidant la grille.
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
{
if (SlotWidget)
{
SlotWidget->RemoveFromParent();
}
}
RecipeSlots.Reset();
// Les lignes de cout aussi : elles affichent l'inventaire du pawn precedent.
for (UCraftingIngredientRowWidget* Row : IngredientRows)
{
if (Row)
{
Row->RemoveFromParent();
}
}
IngredientRows.Reset();
SelectedRecipe = nullptr;
Refresh();
}
void UCraftingWidget::HandleInventoryChanged()
{
// Seules les couleurs et l'etat du bouton bougent : la liste des recettes,
// elle, ne depend que des postes accessibles.
RefreshStates();
}
void UCraftingWidget::HandleStationsChanged()
{
Refresh();
}
void UCraftingWidget::SetStationFilter(ECraftingStation InFilter)
{
if (StationFilter == InFilter)
{
return;
}
StationFilter = InFilter;
Refresh();
}
void UCraftingWidget::Refresh()
{
if (!RecipeGrid)
{
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget::Refresh : le widget nomme 'RecipeGrid' manque dans le Blueprint."));
return;
}
TArray<UCraftingRecipeDataAsset*> Recipes;
if (BoundCrafting.IsValid())
{
BoundCrafting->GatherRecipes(Recipes, StationFilter);
}
RebuildGrid(Recipes);
if (EmptyGridMessage)
{
EmptyGridMessage->SetVisibility(Recipes.Num() > 0 ? ESlateVisibility::Collapsed : ESlateVisibility::Visible);
}
// La recette choisie a pu disparaitre de la liste : on s'est eloigne de
// l'etabli, ou le filtre a change.
if (SelectedRecipe && !Recipes.Contains(SelectedRecipe))
{
SelectedRecipe = nullptr;
}
RefreshStates();
}
void UCraftingWidget::RebuildGrid(const TArray<UCraftingRecipeDataAsset*>& NewRecipes)
{
// La liste ne change qu'en s'approchant d'un poste ou en changeant de
// filtre. Recreer les cases a chaque ramassage serait du gaspillage pur,
// d'ou cette comparaison prealable.
bool bSameList = RecipeSlots.Num() == NewRecipes.Num();
if (bSameList)
{
for (int32 Index = 0; Index < RecipeSlots.Num(); ++Index)
{
if (!RecipeSlots[Index] || RecipeSlots[Index]->GetRecipe() != NewRecipes[Index])
{
bSameList = false;
break;
}
}
}
if (bSameList)
{
return;
}
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
{
if (SlotWidget)
{
SlotWidget->RemoveFromParent();
}
}
RecipeSlots.Reset();
if (NewRecipes.Num() == 0)
{
return;
}
if (!RecipeSlotClass)
{
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : RecipeSlotClass n'est pas assignee, la grille restera vide."));
return;
}
const int32 Columns = FMath::Max(1, ColumnCount);
RecipeSlots.Reserve(NewRecipes.Num());
for (int32 Index = 0; Index < NewRecipes.Num(); ++Index)
{
UCraftingRecipeSlotWidget* NewSlot = CreateWidget<UCraftingRecipeSlotWidget>(this, RecipeSlotClass);
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("UCraftingWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
*GetNameSafe(RecipeSlotClass));
break;
}
NewSlot->InitSlot(NewRecipes[Index], this);
RecipeGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns);
RecipeSlots.Add(NewSlot);
}
}
void UCraftingWidget::RefreshStates()
{
UCraftingComponent* Crafting = BoundCrafting.Get();
for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
{
if (!SlotWidget)
{
continue;
}
ECraftFailureReason Reason = ECraftFailureReason::None;
const bool bCraftable = Crafting && Crafting->CanCraft(SlotWidget->GetRecipe(), Reason);
SlotWidget->SetCraftable(bCraftable);
SlotWidget->SetSelected(SlotWidget->GetRecipe() == SelectedRecipe);
}
RefreshDetails();
}
void UCraftingWidget::RefreshDetails()
{
UCraftingComponent* Crafting = BoundCrafting.Get();
if (DetailsPanel)
{
DetailsPanel->SetVisibility(SelectedRecipe ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
}
if (!SelectedRecipe)
{
if (CraftButton)
{
CraftButton->SetIsEnabled(false);
}
// Le panneau est masque, mais on vide quand meme : le contenu de
// l'ancienne recette reapparaitrait un instant a la selection suivante.
RefreshIngredients();
return;
}
if (ResultIcon)
{
if (UTexture2D* Icon = SelectedRecipe->GetIcon())
{
ResultIcon->SetBrushFromTexture(Icon);
ResultIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
ResultIcon->SetVisibility(ESlateVisibility::Hidden);
}
}
if (ResultNameText)
{
ResultNameText->SetText(SelectedRecipe->GetDisplayName());
}
if (ResultQuantityText)
{
const bool bMultiple = SelectedRecipe->ResultQuantity > 1;
ResultQuantityText->SetVisibility(bMultiple ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
if (bMultiple)
{
ResultQuantityText->SetText(FText::AsNumber(SelectedRecipe->ResultQuantity));
}
}
if (ResultDescriptionText)
{
ResultDescriptionText->SetText(SelectedRecipe->ResultItem ? SelectedRecipe->ResultItem->Description : FText::GetEmpty());
}
RefreshIngredients();
if (CraftButton)
{
ECraftFailureReason Reason = ECraftFailureReason::None;
CraftButton->SetIsEnabled(Crafting && Crafting->CanCraft(SelectedRecipe, Reason));
}
}
void UCraftingWidget::RefreshIngredients()
{
if (!IngredientList)
{
return;
}
TArray<FCraftIngredientStatus> Statuses;
if (SelectedRecipe && BoundCrafting.IsValid())
{
BoundCrafting->GatherIngredientStatus(SelectedRecipe, Statuses);
}
// Trop de lignes : on retire le surplus par la fin.
while (IngredientRows.Num() > Statuses.Num())
{
const int32 Last = IngredientRows.Num() - 1;
if (IngredientRows[Last])
{
IngredientRows[Last]->RemoveFromParent();
}
IngredientRows.RemoveAt(Last);
}
if (IngredientRows.Num() < Statuses.Num() && !IngredientRowClass)
{
UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : IngredientRowClass n'est pas assignee, la liste de cout restera vide."));
}
// Pas assez : on en cree.
while (IngredientRows.Num() < Statuses.Num() && IngredientRowClass)
{
UCraftingIngredientRowWidget* NewRow = CreateWidget<UCraftingIngredientRowWidget>(this, IngredientRowClass);
if (!NewRow)
{
UE_LOG(LogTemp, Error, TEXT("UCraftingWidget : impossible de creer une ligne a partir de %s. Recompile ce Widget Blueprint."),
*GetNameSafe(IngredientRowClass));
break;
}
IngredientList->AddChild(NewRow);
IngredientRows.Add(NewRow);
}
for (int32 Index = 0; Index < IngredientRows.Num(); ++Index)
{
if (IngredientRows[Index] && Statuses.IsValidIndex(Index))
{
IngredientRows[Index]->SetIngredient(Statuses[Index]);
}
}
}
void UCraftingWidget::NotifyRecipeClicked(UCraftingRecipeDataAsset* InRecipe)
{
if (SelectedRecipe == InRecipe)
{
return;
}
SelectedRecipe = InRecipe;
RefreshStates();
}
void UCraftingWidget::HandleCraftClicked()
{
if (!SelectedRecipe || !BoundCrafting.IsValid())
{
return;
}
// Pas de re-verification ici : Craft() refait CanCraft de toute facon, et
// dupliquer la condition finirait par la faire diverger.
BoundCrafting->Craft(SelectedRecipe);
// L'inventaire diffuse son changement, donc RefreshStates est deja passe.
// Rien a rappeler.
}