Files

191 lines
7.1 KiB
C++

// 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<UItemDataAsset> 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<UCraftingRecipeDataAsset*>& 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<FCraftIngredientStatus>& 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<UCraftingRecipeBook> RecipeBook;
/** Resolu une fois a l'initialisation : chercher le composant a chaque clic serait absurde. */
UPROPERTY(Transient, BlueprintReadOnly, Category = "Crafting")
TObjectPtr<UInventoryComponent> 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<ECraftingStation, int32> StationRefCounts;
};