Files
2026-08-16 19:35:27 +02:00

148 lines
4.6 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "HarvestingComponent.h"
#include "DrawDebugHelpers.h"
#include "Engine/World.h"
#include "FpsPlayer.h"
#include "GameFramework/Controller.h"
#include "GameFramework/Pawn.h"
#include "HarvestableResource.h"
#include "ItemDataAsset.h"
UHarvestingComponent::UHarvestingComponent()
{
// Aucun Tick : ce composant ne travaille qu'au moment du coup, une poignee de
// fois par minute. C'est toute la difference avec UInteractionComponent, qui
// doit entretenir un prompt a l'ecran.
PrimaryComponentTick.bCanEverTick = false;
// Pas de SetIsReplicatedByDefault : aucune RPC ne vit ici, elles sont sur le
// pawn. Voir le commentaire de classe.
}
bool UHarvestingComponent::GetViewPoint(FVector& OutLocation, FRotator& OutRotation) const
{
const APawn* OwnerPawn = Cast<APawn>(GetOwner());
if (!OwnerPawn)
{
return false;
}
// Par le controller et pas par la camera : GetPlayerViewPoint renvoie
// exactement ce que le joueur a a l'ecran, secousses et balancement de tete
// compris. Le coup part donc pile du centre du viseur.
if (const AController* OwnerController = OwnerPawn->GetController())
{
OwnerController->GetPlayerViewPoint(OutLocation, OutRotation);
return true;
}
return false;
}
AActor* UHarvestingComponent::TraceSwingTarget(FVector& OutImpactPoint, FVector& OutDirection) const
{
OutImpactPoint = FVector::ZeroVector;
OutDirection = FVector::ForwardVector;
FVector ViewLocation;
FRotator ViewRotation;
if (!GetViewPoint(ViewLocation, ViewRotation))
{
return nullptr;
}
OutDirection = ViewRotation.Vector();
const FVector TraceEnd = ViewLocation + OutDirection * SwingRange;
FCollisionQueryParams Params(SCENE_QUERY_STAT(HarvestSwingTrace), /*bTraceComplex=*/false, GetOwner());
FHitResult Hit;
const bool bHit = GetWorld()->SweepSingleByChannel(
Hit,
ViewLocation,
TraceEnd,
FQuat::Identity,
SwingChannel,
FCollisionShape::MakeSphere(SwingRadius),
Params);
#if ENABLE_DRAW_DEBUG
if (bDrawDebug)
{
const FColor Color = bHit ? FColor::Green : FColor::Red;
DrawDebugLine(GetWorld(), ViewLocation, TraceEnd, Color, false, 1.5f, 0, 1.f);
if (bHit)
{
DrawDebugSphere(GetWorld(), Hit.ImpactPoint, SwingRadius, 12, Color, false, 1.5f);
}
}
#endif
if (!bHit)
{
return nullptr;
}
// ImpactPoint et pas Location : Location est le centre de la sphere balayee a
// l'arret, donc a SwingRadius de la surface. Les copeaux en jailliraient a
// vingt-cinq centimetres du tronc, en plein air.
OutImpactPoint = Hit.ImpactPoint;
return Hit.GetActor();
}
float UHarvestingComponent::GetSwingPower(const UItemDataAsset* Tool) const
{
// Un objet qui n'est pas un outil frappe comme un poing : tenir une pomme ne
// doit ni empecher de cueillir une baie, ni aider a abattre un arbre.
return (Tool && Tool->IsTool()) ? Tool->HarvestPower : UnarmedHarvestPower;
}
EHarvestResult UHarvestingComponent::ApplySwing(AActor* Target, const UItemDataAsset* Tool,
const FVector& ImpactPoint, const FVector& HitDirection)
{
AActor* Owner = GetOwner();
if (!Owner || !Owner->HasAuthority())
{
return EHarvestResult::Invalid;
}
AHarvestableResource* Resource = Cast<AHarvestableResource>(Target);
if (!IsValid(Resource))
{
// Le cas normal, et de loin : la plupart des coups tombent sur le sol, un
// mur, ou rien du tout. Ce n'est pas une erreur, juste un coup dans le vide.
return EHarvestResult::Invalid;
}
// Le client a vise il y a un aller-retour : entre-temps il a bouge, et la
// position que le serveur connait de lui est en retard. On revalide donc, avec
// la meme portee elargie par ServerRangeTolerance.
//
// La distance se mesure a la BOITE ENGLOBANTE de la cible et pas a son origine :
// le pivot d'un arbre de pack est au SOL, donc frapper le tronc a deux metres de
// haut mesurerait une distance bien plus grande que la portee du coup. Le
// symptome serait un grand arbre impossible a abattre pour un client, et
// parfaitement normal chez l'hote.
const FBox TargetBounds = Resource->GetComponentsBoundingBox(/*bNonColliding=*/true);
const float MaxDistanceSq = FMath::Square(SwingRange * ServerRangeTolerance);
if (TargetBounds.ComputeSquaredDistanceToPoint(Owner->GetActorLocation()) > MaxDistanceSq)
{
UE_LOG(LogTemp, Verbose, TEXT("UHarvestingComponent : %s a frappe %s, hors de portee cote serveur."),
*GetNameSafe(Owner), *GetNameSafe(Resource));
return EHarvestResult::Invalid;
}
AFpsPlayer* Harvester = Cast<AFpsPlayer>(Owner);
if (!Harvester)
{
return EHarvestResult::Invalid;
}
return Resource->TryHarvest(Harvester, Tool, GetSwingPower(Tool), ImpactPoint, HitDirection);
}