// 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(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(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(Owner); if (!Harvester) { return EHarvestResult::Invalid; } return Resource->TryHarvest(Harvester, Tool, GetSwingPower(Tool), ImpactPoint, HitDirection); }