// Fill out your copyright notice in the Description page of Project Settings. #include "CraftingComponent.h" #include "CraftingRecipeBook.h" #include "InventoryComponent.h" #include "ItemDataAsset.h" UCraftingComponent::UCraftingComponent() { PrimaryComponentTick.bCanEverTick = false; // Necessaire pour que InitializeComponent() soit appele. bWantsInitializeComponent = true; } void UCraftingComponent::InitializeComponent() { Super::InitializeComponent(); // Les mains sont toujours disponibles. Les postes s'ajoutent par-dessus. AvailableStations.Add(ECraftingStation::Hands); if (const AActor* Owner = GetOwner()) { Inventory = Owner->FindComponentByClass(); } if (!Inventory) { UE_LOG(LogTemp, Warning, TEXT("UCraftingComponent : aucun UInventoryComponent sur %s, aucune fabrication ne sera possible."), *GetNameSafe(GetOwner())); } if (!RecipeBook) { UE_LOG(LogTemp, Warning, TEXT("UCraftingComponent : RecipeBook n'est pas assigne sur %s, la grille de craft restera vide."), *GetNameSafe(GetOwner())); } } void UCraftingComponent::AddAvailableStation(ECraftingStation Station) { if (Station == ECraftingStation::Count) { return; } bool bAlreadyThere = false; AvailableStations.Add(Station, &bAlreadyThere); // On ne diffuse que sur un vrai changement : deux volumes d'etabli qui se // chevauchent rafraichiraient l'interface deux fois pour rien. if (!bAlreadyThere) { OnAvailableStationsChanged.Broadcast(); } } void UCraftingComponent::RemoveAvailableStation(ECraftingStation Station) { // Retirer les mains laisserait le joueur incapable de fabriquer quoi que ce // soit, sans qu'aucun message ne l'explique. if (Station == ECraftingStation::Hands) { return; } if (AvailableStations.Remove(Station) > 0) { OnAvailableStationsChanged.Broadcast(); } } void UCraftingComponent::GatherRecipes(TArray& OutRecipes, ECraftingStation StationFilter) const { OutRecipes.Reset(); if (!RecipeBook) { return; } const TArray>& AllRecipes = RecipeBook->GetRecipes(); OutRecipes.Reserve(AllRecipes.Num()); for (UCraftingRecipeDataAsset* Recipe : AllRecipes) { // Une entree vide dans le livre est signalee par sa validation d'asset. // Ici on se contente de la sauter. if (!Recipe) { continue; } if (!AvailableStations.Contains(Recipe->RequiredStation)) { continue; } if (StationFilter != ECraftingStation::Count && Recipe->RequiredStation != StationFilter) { continue; } OutRecipes.Add(Recipe); } } bool UCraftingComponent::CanCraft(const UCraftingRecipeDataAsset* Recipe, ECraftFailureReason& OutReason) const { OutReason = ECraftFailureReason::None; if (!RecipeBook) { OutReason = ECraftFailureReason::NoRecipeBook; return false; } if (!Recipe || !Recipe->ResultItem) { OutReason = ECraftFailureReason::InvalidRecipe; return false; } if (!Inventory) { OutReason = ECraftFailureReason::InvalidRecipe; return false; } if (!AvailableStations.Contains(Recipe->RequiredStation)) { OutReason = ECraftFailureReason::StationUnavailable; return false; } for (const FCraftIngredient& Ingredient : Recipe->Ingredients) { // Un ingredient nul est refuse par la validation de l'asset. S'il passe // quand meme, on ne fabrique pas : mieux vaut un bouton grise qu'une // recette qui coute moins cher que ce qu'elle affiche. if (!Ingredient.Item) { OutReason = ECraftFailureReason::InvalidRecipe; return false; } if (Inventory->GetItemCount(Ingredient.Item) < Ingredient.Quantity) { OutReason = ECraftFailureReason::MissingIngredients; return false; } } return true; } void UCraftingComponent::GatherIngredientStatus(const UCraftingRecipeDataAsset* Recipe, TArray& OutStatus) const { OutStatus.Reset(); if (!Recipe) { return; } OutStatus.Reserve(Recipe->Ingredients.Num()); for (const FCraftIngredient& Ingredient : Recipe->Ingredients) { if (!Ingredient.Item) { continue; } FCraftIngredientStatus& Status = OutStatus.AddDefaulted_GetRef(); Status.Item = Ingredient.Item; Status.RequiredQuantity = Ingredient.Quantity; Status.OwnedQuantity = Inventory ? Inventory->GetItemCount(Ingredient.Item) : 0; } } bool UCraftingComponent::Craft(UCraftingRecipeDataAsset* Recipe) { ECraftFailureReason Reason = ECraftFailureReason::None; if (!CanCraft(Recipe, Reason)) { return false; } // CanCraft a deja verifie que tout est present : aucun RemoveItem ne peut // echouer a partir d'ici, donc pas de consommation a moitie faite. // // Chaque retrait diffuse OnInventoryChanged, donc l'interface se rafraichit // une fois par ingredient. C'est du gaspillage assume : ca reste quelques // dizaines de SetSlot sur un clic, et grouper les diffusions demanderait // une API de lot dans l'inventaire pour un gain invisible. for (const FCraftIngredient& Ingredient : Recipe->Ingredients) { Inventory->RemoveItem(Ingredient.Item, Ingredient.Quantity); } // -1 : l'objet sort neuf de la fabrication, avec toutes ses charges. const int32 Leftover = Inventory->AddItem(Recipe->ResultItem, Recipe->ResultQuantity, -1); if (Leftover > 0) { if (OnCraftOverflow.IsBound()) { OnCraftOverflow.Broadcast(Recipe->ResultItem, Leftover, -1); } else { // Personne pour poser l'objet au sol : il est perdu. C'est une erreur // de branchement, pas une situation de jeu -- d'ou le niveau Error. UE_LOG(LogTemp, Error, TEXT("UCraftingComponent : %d x %s n'ont pas pu entrer dans l'inventaire et personne n'ecoute OnCraftOverflow. Objets perdus."), Leftover, *GetNameSafe(Recipe->ResultItem)); } } OnCraftSucceeded.Broadcast(Recipe); return true; }