(Feat) Add Harvetable
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include "CraftingComponent.h"
|
||||
#include "FootstepComponent.h"
|
||||
#include "FpsPlayerController.h"
|
||||
#include "HarvestingComponent.h"
|
||||
#include "HeadBobComponent.h"
|
||||
#include "HeldItemComponent.h"
|
||||
#include "InteractionComponent.h"
|
||||
@@ -115,6 +116,7 @@ AFpsPlayer::AFpsPlayer()
|
||||
AppearanceComponent = CreateDefaultSubobject<UCharacterAppearanceComponent>(TEXT("AppearanceComponent"));
|
||||
HeldItemComponent = CreateDefaultSubobject<UHeldItemComponent>(TEXT("HeldItemComponent"));
|
||||
FootstepComponent = CreateDefaultSubobject<UFootstepComponent>(TEXT("FootstepComponent"));
|
||||
HarvestingComponent = CreateDefaultSubobject<UHarvestingComponent>(TEXT("HarvestingComponent"));
|
||||
|
||||
// En vue FPS on ne veut pas voir son propre corps (mesh 3e personne),
|
||||
// mais on garde son ombre.
|
||||
@@ -786,52 +788,113 @@ void AFpsPlayer::TriggerHeldItemAction()
|
||||
return;
|
||||
}
|
||||
|
||||
// Le trace part d'ICI, chez celui qui frappe, et sans condition sur ce qu'il
|
||||
// tient. Deux raisons de ne pas le reserver aux outils : une ressource dont
|
||||
// RequiredTool vaut None se cueille a mains nues, et lire l'inventaire local
|
||||
// pour trier ferait dependre le trace d'un etat qui peut avoir une frame de
|
||||
// retard. Le cout est un unique balayage spherique par CLIC -- pas par frame.
|
||||
FVector ImpactPoint;
|
||||
FVector HitDirection;
|
||||
AActor* SwingTarget = HarvestingComponent
|
||||
? HarvestingComponent->TraceSwingTarget(ImpactPoint, HitDirection)
|
||||
: nullptr;
|
||||
|
||||
// L'hote agit tout de suite, le client demande. On envoie meme si le slot
|
||||
// parait vide chez nous : c'est le serveur qui fait foi, et son inventaire
|
||||
// peut avoir une frame d'avance sur le notre.
|
||||
if (!HasAuthority())
|
||||
{
|
||||
Server_UseItem(PendingUseSlotIndex);
|
||||
Server_UseItem(PendingUseSlotIndex, SwingTarget, ImpactPoint, HitDirection);
|
||||
return;
|
||||
}
|
||||
|
||||
PerformUseItem(PendingUseSlotIndex);
|
||||
PerformUseItem(PendingUseSlotIndex, SwingTarget, ImpactPoint, HitDirection);
|
||||
}
|
||||
|
||||
void AFpsPlayer::Server_UseItem_Implementation(int32 SlotIndex)
|
||||
void AFpsPlayer::Server_UseItem_Implementation(int32 SlotIndex, AActor* SwingTarget,
|
||||
FVector_NetQuantize ImpactPoint, FVector_NetQuantizeNormal HitDirection)
|
||||
{
|
||||
PerformUseItem(SlotIndex);
|
||||
PerformUseItem(SlotIndex, SwingTarget, ImpactPoint, HitDirection);
|
||||
}
|
||||
|
||||
void AFpsPlayer::PerformUseItem(int32 SlotIndex)
|
||||
void AFpsPlayer::PerformUseItem(int32 SlotIndex, AActor* SwingTarget, const FVector& ImpactPoint,
|
||||
const FVector& HitDirection)
|
||||
{
|
||||
if (!HasAuthority() || !InventoryComponent->GetSlots().IsValidIndex(SlotIndex))
|
||||
if (!HasAuthority())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FInventorySlot Selected = InventoryComponent->GetSlots()[SlotIndex];
|
||||
if (Selected.IsEmpty())
|
||||
// Un slot invalide ou vide n'est PAS une sortie : c'est le cas des mains nues,
|
||||
// qui frappent quand meme -- un buisson de baies se cueille au poing. La
|
||||
// version precedente sortait ici, quand seuls les consommables agissaient.
|
||||
const FInventorySlot* Selected = InventoryComponent->GetSlots().IsValidIndex(SlotIndex)
|
||||
? &InventoryComponent->GetSlots()[SlotIndex]
|
||||
: nullptr;
|
||||
|
||||
const UItemDataAsset* Item = (Selected && !Selected->IsEmpty()) ? Selected->Item.Get() : nullptr;
|
||||
|
||||
if (!Item)
|
||||
{
|
||||
PerformToolSwing(nullptr, INDEX_NONE, SwingTarget, ImpactPoint, HitDirection);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Item->bIsConsumable)
|
||||
{
|
||||
// Les valeurs de restauration sont appliquees a CHAQUE utilisation.
|
||||
SurvivalStats->ApplyRestore(Item->HealthRestore, Item->HungerRestore, Item->ThirstRestore);
|
||||
|
||||
// ConsumeUse gere les deux cas : usage unique (l'exemplaire disparait) et
|
||||
// usages multiples (une charge en moins, l'objet reste tant qu'il en a).
|
||||
// On vise le slot actif precisement, et pas RemoveItem qui piocherait
|
||||
// n'importe quelle pile du meme objet, y compris dans le sac.
|
||||
InventoryComponent->ConsumeUse(SlotIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tout le reste frappe : les outils avec leur puissance, les autres objets
|
||||
// comme un poing. Un objet qui n'est ni consommable ni outil ne peut donc rien
|
||||
// contre un arbre -- AHarvestableResource::AcceptsTool l'y refuse -- mais
|
||||
// cueille les ressources a mains nues, ce qui est exactement ce qu'on veut :
|
||||
// tenir une pomme n'empeche pas de ramasser des baies.
|
||||
PerformToolSwing(Item, SlotIndex, SwingTarget, ImpactPoint, HitDirection);
|
||||
}
|
||||
|
||||
void AFpsPlayer::PerformToolSwing(const UItemDataAsset* Tool, int32 SlotIndex, AActor* SwingTarget,
|
||||
const FVector& ImpactPoint, const FVector& HitDirection)
|
||||
{
|
||||
if (!HarvestingComponent || !SwingTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const UItemDataAsset* Item = Selected.Item;
|
||||
const EHarvestResult Result = HarvestingComponent->ApplySwing(SwingTarget, Tool, ImpactPoint, HitDirection);
|
||||
|
||||
// Objet non consommable : on ne fait rien pour l'instant. C'est ici que
|
||||
// viendront les outils (frapper), les armes (tirer), les placeables.
|
||||
if (!Item->bIsConsumable)
|
||||
// SEULS les deux resultats qui portent usent l'outil. Un mauvais outil, une
|
||||
// souche ou un coup dans le vide n'en consomment aucune charge : abimer une
|
||||
// hache en tapant un rocher serait une punition que rien a l'ecran n'explique.
|
||||
if (Result != EHarvestResult::Harvested && Result != EHarvestResult::Depleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Les valeurs de restauration sont appliquees a CHAQUE utilisation.
|
||||
SurvivalStats->ApplyRestore(Item->HealthRestore, Item->HungerRestore, Item->ThirstRestore);
|
||||
if (!Tool || !Tool->IsTool() || SlotIndex == INDEX_NONE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Un outil laisse a MaxUses = 1 disparaitrait au PREMIER coup : ConsumeUse
|
||||
// retire alors l'exemplaire entier. On refuse plutot que de faire s'evaporer
|
||||
// la hache que le joueur vient de fabriquer, et on le dit -- le symptome, lui,
|
||||
// se lit comme « ma hache a disparu » et se cherche tres longtemps.
|
||||
if (Tool->MaxUses <= 1)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : l'outil %s a MaxUses = 1, il casserait au premier coup. Usure ignoree."),
|
||||
*GetNameSafe(Tool));
|
||||
return;
|
||||
}
|
||||
|
||||
// ConsumeUse gere les deux cas : usage unique (l'exemplaire disparait) et
|
||||
// usages multiples (une charge en moins, l'objet reste tant qu'il en a).
|
||||
// On vise le slot actif precisement, et pas RemoveItem qui piocherait
|
||||
// n'importe quelle pile du meme objet, y compris dans le sac.
|
||||
InventoryComponent->ConsumeUse(SlotIndex);
|
||||
}
|
||||
|
||||
@@ -940,6 +1003,11 @@ bool AFpsPlayer::DropFromInventory(UInventoryComponent* Inventory, int32 SlotInd
|
||||
}
|
||||
|
||||
void AFpsPlayer::HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses)
|
||||
{
|
||||
DropAtFeet(Item, Quantity, RemainingUses);
|
||||
}
|
||||
|
||||
void AFpsPlayer::DropAtFeet(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses)
|
||||
{
|
||||
if (!Item || Quantity <= 0)
|
||||
{
|
||||
@@ -948,7 +1016,7 @@ void AFpsPlayer::HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32
|
||||
|
||||
if (!DroppedPickupClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, %d x %s sortis de la fabrication sont perdus."),
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, %d x %s sont perdus."),
|
||||
Quantity, *GetNameSafe(Item));
|
||||
return;
|
||||
}
|
||||
@@ -960,16 +1028,43 @@ void AFpsPlayer::HandleCraftOverflow(UItemDataAsset* Item, int32 Quantity, int32
|
||||
|
||||
// Aux pieds et non devant les yeux comme DropSlot : le joueur fabrique le
|
||||
// nez dans un menu, il ne vise rien. Poser a distance risquerait de traverser
|
||||
// une cloison qu'on ne regarde meme pas.
|
||||
// une cloison qu'on ne regarde meme pas. Le meme raisonnement vaut pour le
|
||||
// surplus de recolte -- le joueur regarde l'arbre, pas le sol.
|
||||
const FVector DropLocation = GetActorLocation() + GetActorForwardVector() * 40.f;
|
||||
|
||||
if (!SpawnPickup(Overflow, PlaceOnGround(DropLocation)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : impossible de poser au sol %d x %s issus de la fabrication."),
|
||||
UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : impossible de poser au sol %d x %s."),
|
||||
Quantity, *GetNameSafe(Item));
|
||||
}
|
||||
}
|
||||
|
||||
int32 AFpsPlayer::GiveItemOrDrop(UItemDataAsset* Item, int32 Quantity, int32 RemainingUses)
|
||||
{
|
||||
if (!Item || Quantity <= 0 || !InventoryComponent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Garde-fou et pas simple precaution : engendrer un pickup chez un client
|
||||
// donnerait un objet visible de lui seul, efface au premier nettoyage reseau.
|
||||
// L'appelant normal -- AHarvestableResource -- est deja cote serveur.
|
||||
if (!HasAuthority())
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer::GiveItemOrDrop appelee sans autorite pour %s, ignoree."),
|
||||
*GetNameSafe(Item));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int32 Rejected = InventoryComponent->AddItem(Item, Quantity, RemainingUses);
|
||||
if (Rejected > 0)
|
||||
{
|
||||
DropAtFeet(Item, Rejected, RemainingUses);
|
||||
}
|
||||
|
||||
return Quantity - Rejected;
|
||||
}
|
||||
|
||||
void AFpsPlayer::DropAllItems()
|
||||
{
|
||||
// Declenche par la mort, qui sera arbitree par le serveur. La garde est la
|
||||
|
||||
Reference in New Issue
Block a user