Files
Unreal_EmberWild/Source/Survival_projet/Private/CraftingComponent.cpp
T
2026-08-03 10:30:47 +02:00

270 lines
7.0 KiB
C++

// 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;
// Aucune propriete repliquee : le composant ne porte que StationRefCounts,
// tenu a jour des deux cotes par le controller. C'est uniquement le routage
// de Server_Craft qui l'exige -- un composant non replique voit ses RPC
// serveur silencieusement jetees.
SetIsReplicatedByDefault(true);
}
void UCraftingComponent::InitializeComponent()
{
Super::InitializeComponent();
// Les mains sont toujours disponibles, et rien ne peut les retirer.
StationRefCounts.Add(ECraftingStation::Hands, 1);
if (const AActor* Owner = GetOwner())
{
Inventory = Owner->FindComponentByClass<UInventoryComponent>();
}
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()));
}
}
bool UCraftingComponent::HasStation(ECraftingStation Station) const
{
const int32* Count = StationRefCounts.Find(Station);
return Count && *Count > 0;
}
void UCraftingComponent::AddAvailableStation(ECraftingStation Station)
{
if (Station == ECraftingStation::Count)
{
return;
}
int32& Count = StationRefCounts.FindOrAdd(Station);
++Count;
// On ne diffuse que sur la transition 0 -> 1. Entrer dans le rayon d'un
// deuxieme etabli ne change rien de visible, inutile de reconstruire la
// grille de recettes pour ca.
if (Count == 1)
{
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;
}
int32* Count = StationRefCounts.Find(Station);
if (!Count || *Count <= 0)
{
return;
}
--(*Count);
if (*Count <= 0)
{
StationRefCounts.Remove(Station);
OnAvailableStationsChanged.Broadcast();
}
}
void UCraftingComponent::GatherRecipes(TArray<UCraftingRecipeDataAsset*>& OutRecipes, ECraftingStation StationFilter) const
{
OutRecipes.Reset();
if (!RecipeBook)
{
return;
}
const TArray<TObjectPtr<UCraftingRecipeDataAsset>>& 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 (!HasStation(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 (!HasStation(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<FCraftIngredientStatus>& 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;
}
}
void UCraftingComponent::Server_Craft_Implementation(UCraftingRecipeDataAsset* Recipe)
{
Craft(Recipe);
}
bool UCraftingComponent::Craft(UCraftingRecipeDataAsset* Recipe)
{
// Fabriquer consomme et ajoute dans l'inventaire, devenu autoritaire.
// Le client demande, le serveur decide -- et il refait CanCraft de son cote
// avec SA liste de postes accessibles : sans ca, un client desynchronise
// fabriquerait une recette d'etabli au milieu de la foret.
const AActor* Owner = GetOwner();
if (!Owner || !Owner->HasAuthority())
{
Server_Craft(Recipe);
// "Demande partie", pas "objet fabrique". Le resultat arrivera par la
// replication de l'inventaire, et OnCraftSucceeded n'est diffuse que
// sur le serveur -- une animation de reussite devra passer par une RPC
// Client_ le jour ou il y en aura une.
return true;
}
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;
}