179 lines
6.6 KiB
C++
179 lines
6.6 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 SURVIVAL_PROJET_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.
|
|
*/
|
|
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:
|
|
/**
|
|
* 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;
|
|
};
|