// 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 Icon; /** Mesh utilise quand l'objet est pose au sol. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item") TObjectPtr 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 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 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 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 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 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 // // Volontairement reduit au TYPE pour l'instant : la puissance de recolte et // le cout en durabilite d'un coup n'ont pas encore de lecteur, et inventer // leur forme avant d'ecrire les ressources recoltables serait deviner. // // L'usure, elle, est deja tranchee : ce sera MaxUses / RemainingUses, comme // une canette. Un outil neuf part donc a MaxUses charges, l'inventaire les // suit deja au sol comme dans un coffre, et WBP_ItemDetails affiche deja la // barre correspondante -- rien a ajouter le jour venu. // ------------------------------------------------------------------ /** Ce que cet objet sait recolter. None = ce n'est pas un outil. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Item|Tool") EToolType ToolType = EToolType::None; 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 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 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 };