// 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(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(); 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 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& 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(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 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(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. }