243 lines
8.8 KiB
C++
243 lines
8.8 KiB
C++
// 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;
|
|
};
|