378 lines
16 KiB
C++
378 lines
16 KiB
C++
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Engine/DataAsset.h"
|
|
#include "ItemDataAsset.generated.h"
|
|
|
|
class UMaterialInterface;
|
|
class UStaticMesh;
|
|
class UTexture2D;
|
|
|
|
/**
|
|
* Famille d'outil, pour l'appariement avec les ressources recoltables.
|
|
*
|
|
* C'est le TYPE qui decide de ce qu'on peut abattre, jamais le mesh ni le nom :
|
|
* un arbre acceptera { Axe }, un filon { Pickaxe }. None n'est pas un outil du
|
|
* tout -- c'est la valeur de tous les objets existants, qui ne changent donc pas
|
|
* de comportement.
|
|
*/
|
|
UENUM(BlueprintType)
|
|
enum class EToolType : uint8
|
|
{
|
|
None UMETA(DisplayName = "Not a tool"),
|
|
Axe,
|
|
Pickaxe,
|
|
|
|
/** Sentinelle de comptage, jamais assignee a un objet. */
|
|
Count UMETA(Hidden)
|
|
};
|
|
|
|
/**
|
|
* Definition d'un TYPE d'objet : "Bois", "Pomme", "Corde".
|
|
* Un asset par type, cree dans le Content Browser, edite a la main.
|
|
*
|
|
* C'est l'equivalent exact d'un ScriptableObject Unity : une donnee partagee
|
|
* que tout le monde reference et que personne ne duplique. Cent buches dans
|
|
* le monde pointent toutes vers le meme UItemDataAsset "Bois".
|
|
*
|
|
* Regle a ne jamais enfreindre : ceci decrit ce qu'est un objet, jamais son
|
|
* etat. La quantite, la durabilite, l'usure appartiennent a l'inventaire ou
|
|
* a l'acteur, pas ici -- sinon ramasser une pomme modifierait toutes les
|
|
* pommes du jeu.
|
|
*/
|
|
UCLASS(BlueprintType)
|
|
class EMBERWILD_API UItemDataAsset : public UPrimaryDataAsset
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
/** Identifiant stable, utilise par la sauvegarde et les recettes de craft. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
|
|
FName ItemId;
|
|
|
|
/** Nom affiche au joueur. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
|
|
FText DisplayName;
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (MultiLine = "true"))
|
|
FText Description;
|
|
|
|
/** Icone affichee dans la grille d'inventaire. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
|
|
TObjectPtr<UTexture2D> Icon;
|
|
|
|
/** Mesh utilise quand l'objet est pose au sol. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
|
|
TObjectPtr<UStaticMesh> WorldMesh;
|
|
|
|
/**
|
|
* Materiau force sur le WorldMesh. Vide = celui du mesh.
|
|
*
|
|
* Indispensable des qu'un pack partage une geometrie entre plusieurs
|
|
* aliments : une chaine de cuisson demande un visuel par etape, et sans ca
|
|
* il faudrait trois meshes distincts la ou un seul suffit avec trois
|
|
* teintes. N'ecrase que le PREMIER slot de materiau -- un mesh qui en
|
|
* expose plusieurs garde les autres tels quels.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item")
|
|
TObjectPtr<UMaterialInterface> WorldMeshMaterial;
|
|
|
|
/**
|
|
* Nombre maximum d'exemplaires par slot.
|
|
* 1 = ne s'empile pas (outils, armes), 20 = ressource classique.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (ClampMin = "1"))
|
|
int32 MaxStackSize = 1;
|
|
|
|
/**
|
|
* Nombre d'utilisations avant que l'objet soit consomme.
|
|
* 1 = objet a usage unique. 3 = une canette qu'on boit en trois gorgees.
|
|
*
|
|
* Les valeurs de restauration sont appliquees A CHAQUE utilisation, pas
|
|
* reparties : une canette a 3 usages et 20 de soif rend 20 par gorgee.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item", meta = (ClampMin = "1"))
|
|
int32 MaxUses = 1;
|
|
|
|
/**
|
|
* Taille de pile reellement applicable.
|
|
*
|
|
* Un objet a charges ne s'empile jamais : deux canettes entamees a des
|
|
* niveaux differents ne peuvent pas partager un slot, il faudrait stocker
|
|
* deux compteurs dans une seule case. La regle est centralisee ici pour
|
|
* qu'aucun appelant ne puisse l'oublier.
|
|
*/
|
|
UFUNCTION(BlueprintPure, Category = "Item")
|
|
int32 GetEffectiveMaxStack() const { return MaxUses > 1 ? 1 : FMath::Max(1, MaxStackSize); }
|
|
|
|
/**
|
|
* Pose le mesh et le materiau de l'objet sur un composant.
|
|
*
|
|
* Un DataAsset ne devrait pas piloter un composant, mais la regle "mesh plus
|
|
* materiau optionnel" a deja deux appelants -- l'objet au sol et l'objet sur
|
|
* le feu -- et en aura d'autres, l'objet en main le premier. La dupliquer
|
|
* garantit qu'un jour l'un des deux oubliera le materiau.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Item")
|
|
void ApplyWorldVisualsTo(class UStaticMeshComponent* Component) const;
|
|
|
|
/**
|
|
* Slots ajoutes a l'inventaire quand cet objet est equipe comme sac.
|
|
* Laisse a 0 pour tout ce qui n'est pas un contenant.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Backpack", meta = (ClampMin = "0"))
|
|
int32 BonusInventorySlots = 0;
|
|
|
|
// ------------------------------------------------------------------
|
|
// En main
|
|
//
|
|
// Ce qu'on voit quand l'objet occupe la case active de la barre rapide.
|
|
// C'est UHeldItemComponent qui lit ce bloc ; il ne concerne donc que le
|
|
// visuel, jamais ce que l'objet fait.
|
|
// ------------------------------------------------------------------
|
|
|
|
/**
|
|
* Cet objet apparait-il dans la main quand il est selectionne.
|
|
*
|
|
* Faux par defaut, et c'est delibere : les deux offsets ci-dessous se reglent
|
|
* a l'oeil, objet par objet, sur un socket donne. Un vrai par defaut ferait
|
|
* surgir toutes les ressources deja creees dans le poing du joueur, chacune
|
|
* de travers, sans que personne l'ait demande.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held")
|
|
bool bShowInHand = false;
|
|
|
|
/**
|
|
* Couche d'animation des BRAS quand cet objet est en main (ABP_Tool_Axe...).
|
|
*
|
|
* C'est un « Linked Anim Layer » : un Animation Blueprint qui implemente
|
|
* l'interface de couche de ABP_FpsArms et REMPLACE ses cycles le temps qu'on
|
|
* tienne l'objet. Vide = les bras gardent leurs cycles mains nues.
|
|
*
|
|
* Ce champ est la raison d'etre de tout le mecanisme, et elle est humaine
|
|
* plutot que technique : ajouter un outil ne doit demander NI recompilation
|
|
* C++, NI ouverture de ABP_FpsArms. Un animateur livre son AnimBP de couche,
|
|
* on le glisse ici, c'est fini. L'alternative -- un enum de posture lu par un
|
|
* « Blend Poses by Enum » -- coutait une valeur d'enum, une recompilation, et
|
|
* un branchement a la main dans CHAQUE etat de la machine : le travail
|
|
* croissait en outils x etats, pas en outils.
|
|
*
|
|
* Contrainte a tenir : la couche doit etre batie sur le meme squelette que
|
|
* les bras, et implementer la meme interface. Une classe qui n'implemente
|
|
* rien se lie sans erreur et ne change simplement rien -- symptome silencieux
|
|
* dont il faut se souvenir.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held", meta = (EditCondition = "bShowInHand"))
|
|
TSubclassOf<class UAnimInstance> ArmsAnimLayer;
|
|
|
|
/**
|
|
* Mesh tenu en main. Vide = on reprend WorldMesh.
|
|
*
|
|
* A ne remplir que si l'objet au sol et l'objet en main different vraiment --
|
|
* une buche posee en tas contre une buche unique, typiquement. Une hache,
|
|
* elle, est la meme des deux cotes.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held", meta = (EditCondition = "bShowInHand"))
|
|
TObjectPtr<UStaticMesh> HeldMesh;
|
|
|
|
/**
|
|
* Ajustement du mesh sur le socket des BRAS (vue premiere personne).
|
|
*
|
|
* Necessaire parce qu'on s'attache a un OS du rig et pas a un socket taille
|
|
* pour l'objet : le pivot d'un mesh d'outil tombe presque toujours sur son
|
|
* centre, jamais sur le manche. Se regle a l'oeil en PIE.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held", meta = (EditCondition = "bShowInHand"))
|
|
FTransform FirstPersonHeldOffset;
|
|
|
|
/**
|
|
* Ajustement sur la main du CORPS (ce que les autres joueurs voient).
|
|
*
|
|
* Separe du precedent, et il le restera : les bras FPS et le mannequin UE5
|
|
* sont deux rigs distincts, avec deux echelles et deux orientations d'os. Une
|
|
* transform commune donnerait forcement une hache de travers d'un cote.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held", meta = (EditCondition = "bShowInHand"))
|
|
FTransform ThirdPersonHeldOffset;
|
|
|
|
/**
|
|
* Geste joue sur les BRAS quand cet objet arrive en main (MTG_Axe_Equip...).
|
|
*
|
|
* Un montage et pas un etat de la machine a etats : sortir un outil est un
|
|
* geste PONCTUEL qui doit se superposer a ce que les bras faisaient deja --
|
|
* meme raison que LandMontage. Un etat imposerait de decider combien de temps
|
|
* y rester, et changer d'outil en pleine course y bloquerait la foulee.
|
|
*
|
|
* Vide = l'objet apparait sans geste, ce qui reste parfaitement jouable.
|
|
* Rien n'est replique : les bras ne sont vus que par leur proprietaire.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held")
|
|
TObjectPtr<class UAnimMontage> EquipMontage;
|
|
|
|
/**
|
|
* Geste du CLIC GAUCHE avec cet objet en main (MTG_Pickaxe_Attack...).
|
|
*
|
|
* « Use » et pas « Attack » : le clic gauche est une seule action dont le
|
|
* sens depend de ce qu'on tient -- frapper avec une pioche, boire a une
|
|
* gourde, allumer une torche. Un champ par verbe finirait en AttackMontage,
|
|
* EatMontage, DrinkMontage pour un seul bouton.
|
|
*
|
|
* Il ne fait QUE l'animation. Ce que l'objet produit reste dans
|
|
* AFpsPlayer::PerformUseItem, cote serveur -- l'un se voit tout de suite chez
|
|
* soi, l'autre doit faire autorite.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Held")
|
|
TObjectPtr<class UAnimMontage> UseMontage;
|
|
|
|
/** Le mesh a montrer en main, en appliquant le repli sur WorldMesh. */
|
|
UFUNCTION(BlueprintPure, Category = "Item|Held")
|
|
UStaticMesh* GetHeldMesh() const { return HeldMesh ? HeldMesh.Get() : WorldMesh.Get(); }
|
|
|
|
/**
|
|
* Pose le mesh tenu en main et son materiau sur un composant.
|
|
* Pendant exact d'ApplyWorldVisualsTo, dont c'est le second appelant annonce.
|
|
*/
|
|
UFUNCTION(BlueprintCallable, Category = "Item|Held")
|
|
void ApplyHeldVisualsTo(class UStaticMeshComponent* Component) const;
|
|
|
|
// ------------------------------------------------------------------
|
|
// Outil
|
|
//
|
|
// L'usure passe par MaxUses / RemainingUses, comme une canette : un outil neuf
|
|
// part a MaxUses charges, l'inventaire les suit deja au sol comme dans un
|
|
// coffre, et WBP_ItemDetails affiche deja la barre correspondante. Un coup qui
|
|
// PORTE consomme une charge -- un coup dans le vide ou contre la mauvaise
|
|
// matiere n'use rien, voir UHarvestingComponent.
|
|
//
|
|
// Corollaire a connaitre : un outil laisse a MaxUses = 1 casserait au premier
|
|
// coup. AFpsPlayer::PerformUseItem s'y refuse et le dit dans le log, plutot que
|
|
// de faire disparaitre la hache que le joueur vient de fabriquer.
|
|
// ------------------------------------------------------------------
|
|
|
|
/** Ce que cet objet sait recolter. None = ce n'est pas un outil. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Tool")
|
|
EToolType ToolType = EToolType::None;
|
|
|
|
/**
|
|
* Points retires a une ressource par coup.
|
|
*
|
|
* A comparer au Health de UHarvestableDataAsset : 25 contre un arbre a 100
|
|
* donne quatre coups. C'est ce champ, et lui seul, qui fait la difference entre
|
|
* la hache de pierre et celle en fer -- la ressource, elle, ne change pas.
|
|
*
|
|
* Sans effet sur une ressource que ToolType n'autorise pas : une pioche a 100
|
|
* de puissance ne coupe toujours pas un arbre.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Tool",
|
|
meta = (EditCondition = "ToolType != EToolType::None", ClampMin = "0"))
|
|
float HarvestPower = 25.f;
|
|
|
|
UFUNCTION(BlueprintPure, Category = "Item|Tool")
|
|
bool IsTool() const { return ToolType != EToolType::None; }
|
|
|
|
// ------------------------------------------------------------------
|
|
// Consommable
|
|
// ------------------------------------------------------------------
|
|
|
|
/** Coche pour qu'un clic gauche consomme l'objet et applique ses effets. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consumable")
|
|
bool bIsConsumable = false;
|
|
|
|
/** Faim rendue a la consommation. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consumable", meta = (EditCondition = "bIsConsumable"))
|
|
float HungerRestore = 0.f;
|
|
|
|
/** Soif rendue. Une valeur negative deshydrate -- utile pour l'eau salee. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consumable", meta = (EditCondition = "bIsConsumable"))
|
|
float ThirstRestore = 0.f;
|
|
|
|
/** Vie rendue. Negative pour un aliment avarie ou toxique. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Consumable", meta = (EditCondition = "bIsConsumable"))
|
|
float HealthRestore = 0.f;
|
|
|
|
// ------------------------------------------------------------------
|
|
// Cuisson
|
|
//
|
|
// Modele en CHAINE D'OBJETS, sans recette : cuire n'est pas un choix que le
|
|
// joueur fait dans un menu, c'est une propriete de l'objet. "Steak cru"
|
|
// pointe vers "Steak cuit", qui pointe vers "Steak brule". Chaque maillon
|
|
// est un asset complet, donc le changement de mesh sur le feu ne coute pas
|
|
// une ligne : c'est le WorldMesh du maillon suivant.
|
|
//
|
|
// Ce qui n'est PAS ici, et ne doit jamais y venir : le temps deja passe sur
|
|
// le feu. C'est de l'etat, il vit sur ACookingStation. Le mettre dans le
|
|
// DataAsset ferait cuire tous les steaks du monde en meme temps.
|
|
// ------------------------------------------------------------------
|
|
|
|
/** Cet objet se transforme quand on le pose sur un feu allume. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking")
|
|
bool bCanBeCooked = false;
|
|
|
|
/** Secondes de feu ACTIF necessaires. Un feu eteint met le compteur en pause. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking", meta = (EditCondition = "bCanBeCooked", ClampMin = "0.1", Units = "s"))
|
|
float CookDuration = 20.f;
|
|
|
|
/** Ce que l'objet devient une fois cuit. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking", meta = (EditCondition = "bCanBeCooked"))
|
|
TObjectPtr<UItemDataAsset> CookedResult;
|
|
|
|
/**
|
|
* L'objet se degrade s'il reste sur le feu.
|
|
*
|
|
* Volontairement independant de bCanBeCooked : un objet peut bruler sans
|
|
* jamais avoir ete cuisinable (une planche jetee dans les flammes), et un
|
|
* plat peut rester indefiniment au chaud sans jamais se gacher. Une seule
|
|
* chaine commune imposerait l'un ou l'autre a tout le monde.
|
|
*
|
|
* Si les deux sont coches, la CUISSON passe en premier : c'est le maillon
|
|
* cuit qui portera bCanBurn pour continuer vers le brule.
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking")
|
|
bool bCanBurn = false;
|
|
|
|
/** Secondes de feu actif avant que l'objet se gache. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking", meta = (EditCondition = "bCanBurn", ClampMin = "0.1", Units = "s"))
|
|
float BurnDuration = 40.f;
|
|
|
|
/** Ce que l'objet devient une fois brule. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Cooking", meta = (EditCondition = "bCanBurn"))
|
|
TObjectPtr<UItemDataAsset> BurntResult;
|
|
|
|
/**
|
|
* Le feu accepte-t-il cet objet sur un emplacement de cuisson.
|
|
*
|
|
* Un maillon final -- le steak brule -- repond non : on ne peut plus le
|
|
* poser, et il ne fait plus rien s'il est deja la. La regle est centralisee
|
|
* ici pour que la station n'ait pas a la reconstruire a chaque appel.
|
|
*/
|
|
UFUNCTION(BlueprintPure, Category = "Item|Cooking")
|
|
bool CanBePlacedOnFire() const
|
|
{
|
|
return (bCanBeCooked && CookedResult != nullptr) || (bCanBurn && BurntResult != nullptr);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Carburant
|
|
// ------------------------------------------------------------------
|
|
|
|
/** Cet objet peut alimenter un feu. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Fuel")
|
|
bool bIsFuel = false;
|
|
|
|
/** Secondes de combustion apportees par exemplaire. */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Fuel", meta = (EditCondition = "bIsFuel", ClampMin = "1", Units = "s"))
|
|
float FuelDuration = 60.f;
|
|
|
|
#if WITH_EDITOR
|
|
/**
|
|
* Une chaine de cuisson mal branchee ne planterait pas : la viande resterait
|
|
* simplement crue sur le feu, sans rien dire. C'est exactement le genre de
|
|
* bug qu'on cherche une heure -- autant que l'editeur le refuse a la
|
|
* sauvegarde.
|
|
*/
|
|
virtual EDataValidationResult IsDataValid(class FDataValidationContext& Context) const override;
|
|
#endif
|
|
};
|