(Feat) Add Harvetable
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/NetSerialization.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "HarvestableResource.generated.h"
|
||||
|
||||
class AFpsPlayer;
|
||||
class UHarvestableDataAsset;
|
||||
class UItemDataAsset;
|
||||
class UStaticMesh;
|
||||
class UStaticMeshComponent;
|
||||
|
||||
/**
|
||||
* Ce qu'a produit un coup, du point de vue de celui qui frappe.
|
||||
*
|
||||
* Un enum et pas un booleen, parce que l'appelant en tire trois conduites
|
||||
* distinctes : un coup qui porte use l'outil et rend du butin, un mauvais outil
|
||||
* ne doit RIEN user -- sinon taper un rocher a la hache la deteriore sans rien
|
||||
* donner, ce que personne ne peut comprendre -- et une ressource deja epuisee
|
||||
* n'est pas une erreur du joueur, juste un geste sans effet.
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EHarvestResult : uint8
|
||||
{
|
||||
/** Le coup a porte : du butin est parti, l'outil s'use. */
|
||||
Harvested,
|
||||
|
||||
/** Le coup a epuise la ressource : butin final compris. */
|
||||
Depleted,
|
||||
|
||||
/** L'outil en main ne peut rien contre cette ressource. */
|
||||
WrongTool,
|
||||
|
||||
/** Deja epuisee ; elle repoussera peut-etre. */
|
||||
AlreadyDepleted,
|
||||
|
||||
/** Cible invalide, donnee manquante : rien ne s'est produit. */
|
||||
Invalid
|
||||
};
|
||||
|
||||
/** Ce que le multicast de retour doit jouer. Un seul RPC pour les trois cas. */
|
||||
UENUM()
|
||||
enum class EHarvestFeedback : uint8
|
||||
{
|
||||
Hit,
|
||||
FinalBlow,
|
||||
Ineffective
|
||||
};
|
||||
|
||||
/**
|
||||
* Un arbre, un rocher, un buisson : quelque chose qu'on frappe pour en tirer des
|
||||
* ressources.
|
||||
*
|
||||
* Ce n'est PAS un IInteractable, et c'est la decision structurante : on ne
|
||||
* « valide » pas un arbre, on le frappe. Le coup part du marqueur
|
||||
* UAnimNotify_ItemAction pose dans le montage de l'outil, donc au moment ou la
|
||||
* hache touche -- jamais a l'appui. L'acteur n'a donc ni prompt, ni volume de
|
||||
* visee dedie : le trace du coup vient toucher son MESH, comme un vrai coup.
|
||||
*
|
||||
* ------------------------------------------------------------------
|
||||
* Ou vit quoi
|
||||
* ------------------------------------------------------------------
|
||||
*
|
||||
* Les REGLES (outil requis, points de vie, butin, repousse) sont dans le
|
||||
* UHarvestableDataAsset, partage par toute l'espece. Les MESHES sont ici, sur
|
||||
* l'acteur : les quinze sapins du pack ont quinze visuels et une seule regle.
|
||||
* Mettre le mesh sur la donnee obligerait a un DataAsset par variante, mettre la
|
||||
* regle sur l'acteur obligerait a re-regler le butin sur quatre cents instances.
|
||||
*
|
||||
* ------------------------------------------------------------------
|
||||
* Reseau : le serveur compte, les clients regardent
|
||||
* ------------------------------------------------------------------
|
||||
*
|
||||
* CurrentHealth n'est PAS repliquee et ne doit jamais l'etre : c'est un compteur
|
||||
* qui change a chaque coup pour un resultat que le joueur lit deja dans le
|
||||
* balancement du tronc et dans les objets qui arrivent dans son sac. Seul
|
||||
* bDepleted part sur le reseau -- une transition, pas un flux.
|
||||
*/
|
||||
UCLASS()
|
||||
class EMBERWILD_API AHarvestableResource : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AHarvestableResource();
|
||||
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
|
||||
|
||||
/**
|
||||
* Encaisse un coup. A n'appeler que sur le SERVEUR.
|
||||
*
|
||||
* @param Harvester celui qui frappe ; c'est son inventaire qui recoit.
|
||||
* @param Tool l'objet en main, nul si les mains sont vides.
|
||||
* @param Power points retires par ce coup.
|
||||
* @param ImpactPoint point touche par le trace, pour les copeaux.
|
||||
* @param HitDirection direction du coup, normalisee, pour le sens du balancement.
|
||||
*/
|
||||
EHarvestResult TryHarvest(AFpsPlayer* Harvester, const UItemDataAsset* Tool, float Power,
|
||||
const FVector& ImpactPoint, const FVector& HitDirection);
|
||||
|
||||
/** Vrai si l'outil fourni (nul = mains nues) peut entamer cette ressource. */
|
||||
UFUNCTION(BlueprintPure, Category = "Harvestable")
|
||||
bool AcceptsTool(const UItemDataAsset* Tool) const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Harvestable")
|
||||
bool IsDepleted() const { return bDepleted; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "Harvestable")
|
||||
UHarvestableDataAsset* GetHarvestData() const { return HarvestData; }
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/**
|
||||
* Le visuel ET la surface qu'on frappe.
|
||||
*
|
||||
* Le mesh porte lui-meme la collision, comme ACraftingStation et contrairement
|
||||
* a APickupItem : un arbre est un obstacle, il a forcement une collision, et un
|
||||
* volume ajoute par-dessus laisserait frapper le vide autour du tronc.
|
||||
* Corollaire a verifier sur chaque mesh de pack : sans collision simple, le
|
||||
* trace du coup ne rencontre rien et l'arbre est intouchable.
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Harvestable")
|
||||
TObjectPtr<UStaticMeshComponent> Mesh;
|
||||
|
||||
/** Les regles de recolte. Sans elle l'acteur est un simple decor. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Harvestable")
|
||||
TObjectPtr<UHarvestableDataAsset> HarvestData;
|
||||
|
||||
/**
|
||||
* Ce qu'on voit une fois la ressource epuisee : la souche, le filon vide.
|
||||
*
|
||||
* Vide = l'acteur se cache entierement, collision comprise. C'est un repli
|
||||
* acceptable pour un buisson de baies -- personne ne remarque un buisson
|
||||
* absent -- mais un arbre qui s'evapore se voit immediatement : donne-lui une
|
||||
* souche.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Harvestable")
|
||||
TObjectPtr<UStaticMesh> DepletedMesh;
|
||||
|
||||
/**
|
||||
* Epuisee, en attente de repousse.
|
||||
*
|
||||
* ReplicatedUsing et pas simplement Replicated : c'est le meme piege que
|
||||
* APickupItem::ItemData. Un client qui rejoint une partie en cours construit
|
||||
* l'acteur AVANT que la propriete arrive -- il verrait l'arbre intact et
|
||||
* pourrait le frapper, alors que le serveur le sait deja abattu.
|
||||
*/
|
||||
UPROPERTY(ReplicatedUsing = OnRep_Depleted, BlueprintReadOnly, Category = "Harvestable")
|
||||
bool bDepleted = false;
|
||||
|
||||
UFUNCTION()
|
||||
void OnRep_Depleted();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Points restants. SERVEUR UNIQUEMENT -- voir le commentaire de classe.
|
||||
* Initialisee au BeginPlay depuis la donnee, et remise a plein a la repousse.
|
||||
*/
|
||||
float CurrentHealth = 0.f;
|
||||
|
||||
/**
|
||||
* Le mesh d'origine, retenu au BeginPlay pour le reposer a la repousse.
|
||||
*
|
||||
* Retenu et surtout pas relu depuis le CDO de la classe : une instance posee
|
||||
* dans le niveau peut avoir son propre mesh -- c'est meme le cas normal, un
|
||||
* seul BP_Harvestable servant les quinze variantes d'arbre du pack. Le CDO
|
||||
* rendrait alors le mesh du Blueprint, et l'arbre repousserait en sapin
|
||||
* generique. Le symptome n'apparait qu'apres RegrowDelay secondes.
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UStaticMesh> IntactMesh;
|
||||
|
||||
/**
|
||||
* Verse le contenu d'une table dans l'inventaire du recolteur.
|
||||
* @return le nombre de lignes qui ont reellement donne quelque chose.
|
||||
*/
|
||||
int32 GrantDrops(const TArray<struct FHarvestDrop>& Table, AFpsPlayer* Harvester);
|
||||
|
||||
/** Passe la ressource a l'etat epuise et arme la repousse. Serveur. */
|
||||
void Deplete();
|
||||
|
||||
/** Remet la ressource d'aplomb. Appelee par le timer, donc serveur. */
|
||||
void Regrow();
|
||||
|
||||
/**
|
||||
* Pose le mesh correspondant a bDepleted. Tourne sur TOUTES les machines.
|
||||
*
|
||||
* Partagee par BeginPlay et OnRep : deux copies finiraient par diverger, et le
|
||||
* symptome serait une souche correcte chez l'hote et un arbre intact chez le
|
||||
* client qui vient de le voir tomber.
|
||||
*/
|
||||
void ApplyDepletionVisuals();
|
||||
|
||||
/**
|
||||
* Retour audiovisuel du coup, joue chez tout le monde.
|
||||
*
|
||||
* Unreliable, et c'est un choix : un paquet perdu coute un bruit de hache, pas
|
||||
* un etat de jeu. Le fiabiliser ferait payer un accuse de reception a chacun
|
||||
* des trois autres joueurs pour chaque coup porte dans la partie.
|
||||
*/
|
||||
UFUNCTION(NetMulticast, Unreliable)
|
||||
void Multicast_PlayHarvestFeedback(EHarvestFeedback Feedback, FVector_NetQuantize ImpactPoint,
|
||||
FVector_NetQuantizeNormal HitDirection);
|
||||
|
||||
/** Joue son, particules et balancement. Locale a chaque machine. */
|
||||
void PlayHarvestFeedback(EHarvestFeedback Feedback, const FVector& ImpactPoint, const FVector& HitDirection);
|
||||
|
||||
/** Lance le balancement. Axe deduit de la direction du coup. */
|
||||
void StartShake(const FVector& HitDirection);
|
||||
|
||||
/**
|
||||
* Temps ecoule dans le balancement en cours, en secondes.
|
||||
* Negatif = aucun balancement, donc le Tick est coupe.
|
||||
*/
|
||||
float ShakeElapsed = -1.f;
|
||||
|
||||
/**
|
||||
* Axe autour duquel le mesh oscille, en espace LOCAL.
|
||||
*
|
||||
* En local et pas en monde : le mesh tourne dans le repere de son parent, et
|
||||
* un arbre pose avec un yaw quelconque ferait pencher le tronc de travers si
|
||||
* l'axe restait exprime en monde.
|
||||
*/
|
||||
FVector ShakeAxis = FVector::RightVector;
|
||||
|
||||
/**
|
||||
* Rotation d'origine du mesh, pour y revenir exactement.
|
||||
*
|
||||
* Retenue et jamais recalculee : accumuler des AddRelativeRotation laisse
|
||||
* derriver l'arbre a chaque coup, et au bout de vingt coups il pousse
|
||||
* franchement de travers. Le symptome ne sort qu'apres une longue session.
|
||||
*/
|
||||
FRotator BaseMeshRotation = FRotator::ZeroRotator;
|
||||
|
||||
/** Repousse, cote serveur uniquement. */
|
||||
FTimerHandle RegrowTimer;
|
||||
};
|
||||
Reference in New Issue
Block a user