// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Components/ActorComponent.h" #include "CraftingRecipeDataAsset.h" #include "CraftingComponent.generated.h" class UCraftingRecipeBook; class UInventoryComponent; class UItemDataAsset; /** * Pourquoi une fabrication a echoue. Sert a l'interface, pour dire au joueur * ce qui manque au lieu de laisser un bouton grise sans explication. */ UENUM(BlueprintType) enum class ECraftFailureReason : uint8 { None, InvalidRecipe, NoRecipeBook, StationUnavailable, MissingIngredients }; /** * Etat d'un ingredient pour une recette donnee : ce qu'il faut, ce qu'on a. * * C'est le composant qui fait le rapprochement, pas le widget. Une ligne * d'ingredient n'a plus qu'a afficher deux nombres et choisir une couleur -- * elle n'interroge jamais l'inventaire elle-meme. */ USTRUCT(BlueprintType) struct FCraftIngredientStatus { GENERATED_BODY() UPROPERTY(BlueprintReadOnly, Category = "Crafting") TObjectPtr Item = nullptr; UPROPERTY(BlueprintReadOnly, Category = "Crafting") int32 RequiredQuantity = 0; UPROPERTY(BlueprintReadOnly, Category = "Crafting") int32 OwnedQuantity = 0; bool IsSatisfied() const { return OwnedQuantity >= RequiredQuantity; } }; /** Une fabrication vient d'aboutir. Pour le son, l'animation, une notification. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCraftSucceeded, UCraftingRecipeDataAsset*, Recipe); /** Les postes accessibles ont change : l'interface doit revoir sa liste. */ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAvailableStationsChanged); /** * Le resultat n'est pas entre dans l'inventaire. A quelqu'un de le poser * dans le monde -- voir le commentaire de Craft(). */ DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnCraftOverflow, UItemDataAsset*, Item, int32, Quantity, int32, RemainingUses); /** * Fabrication d'objets a partir d'un livre de recettes et de l'inventaire du * meme acteur. * * Sur le pawn : fabriquer est une capacite du corps, au meme titre que porter * un sac. Le composant ne connait ni l'interface ni la classe du pawn -- un * futur etabli pourra porter le meme composant avec son propre livre. */ UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent)) class EMBERWILD_API UCraftingComponent : public UActorComponent { GENERATED_BODY() public: UCraftingComponent(); virtual void InitializeComponent() override; /** * Recettes a afficher, dans l'ordre du livre. * * Ne renvoie que celles dont le poste est ACCESSIBLE : les mains vides, on * ne voit pas les recettes d'etabli. C'est le modele choisi -- une seule * fenetre de craft dont le contenu depend d'ou l'on se trouve. * * @param StationFilter filtre d'affichage supplementaire, purement visuel. * Passe Count pour tout voir. Un poste demande ici mais non * accessible ne fait rien apparaitre : le filtre restreint, il * n'autorise jamais. * * Pas de valeur par defaut sur StationFilter : UHT refuse une entree * UMETA(Hidden) comme defaut, et dé-cacher Count le rendrait selectionnable * dans le champ Required Station d'une recette. L'appelant l'ecrit. */ UFUNCTION(BlueprintCallable, Category = "Crafting") void GatherRecipes(TArray& OutRecipes, ECraftingStation StationFilter) const; /** Peut-on fabriquer cette recette maintenant, et sinon pourquoi. */ UFUNCTION(BlueprintCallable, Category = "Crafting") bool CanCraft(const UCraftingRecipeDataAsset* Recipe, ECraftFailureReason& OutReason) const; /** Le detail requis / possede de chaque ingredient, dans l'ordre de la recette. */ UFUNCTION(BlueprintCallable, Category = "Crafting") void GatherIngredientStatus(const UCraftingRecipeDataAsset* Recipe, TArray& OutStatus) const; /** * Consomme les ingredients et ajoute le resultat a l'inventaire. * * L'ordre compte : on consomme AVANT d'ajouter. Refuser une fabrication * parce que l'inventaire est plein serait faux -- consommer trois branches * libere justement la case ou poser le resultat. Et si malgre tout il ne * rentre pas, l'objet part dans OnCraftOverflow pour etre pose au sol : * aucune ressource ne disparait jamais, ni a l'entree ni a la sortie. * * @return true si la fabrication a eu lieu. */ /** * Fabrique la recette. Chez un client, envoie la demande au serveur et rend * true : le resultat redescendra par la replication de l'inventaire. * * Le serveur refait TOUJOURS CanCraft de son cote, avec sa propre liste de * postes accessibles -- celle que le controller tient a jour par * Client_OpenCraftingAtStation et Server_ReleaseStation. */ UFUNCTION(BlueprintCallable, Category = "Crafting") bool Craft(UCraftingRecipeDataAsset* Recipe); // ------------------------------------------------------------------ // Postes accessibles // // Etat courant, pas de la configuration : {Hands} en permanence, plus // Workbench tant qu'on est devant un etabli. C'est une CONDITION de // fabrication, a ne pas confondre avec le filtre d'affichage de // GatherRecipes -- melanger les deux rendrait un jour un craft impossible // a cause d'un bouton d'interface. // ------------------------------------------------------------------ UFUNCTION(BlueprintPure, Category = "Crafting|Stations") bool HasStation(ECraftingStation Station) const; /** * A appeler quand le joueur entre dans le rayon d'un poste. * * Les appels se COMPTENT : deux etablis dont les portees se chevauchent, ce * qui arrive des qu'on construit une base un peu dense, s'ajoutent tous les * deux. Sortir du premier ne doit pas retirer l'acces alors qu'on est encore * devant le second. */ UFUNCTION(BlueprintCallable, Category = "Crafting|Stations") void AddAvailableStation(ECraftingStation Station); /** A appeler quand il s'en eloigne. Hands ne peut pas etre retire. */ UFUNCTION(BlueprintCallable, Category = "Crafting|Stations") void RemoveAvailableStation(ECraftingStation Station); UPROPERTY(BlueprintAssignable, Category = "Crafting") FOnCraftSucceeded OnCraftSucceeded; UPROPERTY(BlueprintAssignable, Category = "Crafting") FOnAvailableStationsChanged OnAvailableStationsChanged; UPROPERTY(BlueprintAssignable, Category = "Crafting") FOnCraftOverflow OnCraftOverflow; protected: /** Ton DA_RecipeBook. Sans lui le composant ne connait aucune recette. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Crafting") TObjectPtr RecipeBook; /** Resolu une fois a l'initialisation : chercher le composant a chaque clic serait absurde. */ UPROPERTY(Transient, BlueprintReadOnly, Category = "Crafting") TObjectPtr Inventory; private: /** La fabrication demandee par un client, arbitree par le serveur. */ UFUNCTION(Server, Reliable) void Server_Craft(UCraftingRecipeDataAsset* Recipe); /** * Combien de postes de chaque type sont a portee, et non un simple * "present / absent" -- voir AddAvailableStation. * * Pas une UPROPERTY : ni la cle ni la valeur ne sont des references a * collecter, et cet etat n'a rien a regler dans l'editeur. */ TMap StationRefCounts; };