// Copyright Epic Games, Inc. All Rights Reserved. #include "AbilityTask_WaitForInteractableTargets.h" #include "GameFramework/Actor.h" #include "Physics/LyraCollisionChannels.h" #include "Interaction/IInteractableTarget.h" #include "Interaction/InteractionStatics.h" #include "Interaction/InteractionQuery.h" #include "AbilitySystemComponent.h" #include "GameFramework/PlayerController.h" UAbilityTask_WaitForInteractableTargets::UAbilityTask_WaitForInteractableTargets(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void UAbilityTask_WaitForInteractableTargets::LineTrace(FHitResult& OutHitResult, const UWorld* World, const FVector& Start, const FVector& End, FName ProfileName, const FCollisionQueryParams Params) { check(World); OutHitResult = FHitResult(); TArray HitResults; World->LineTraceMultiByProfile(HitResults, Start, End, ProfileName, Params); OutHitResult.TraceStart = Start; OutHitResult.TraceEnd = End; if (HitResults.Num() > 0) { OutHitResult = HitResults[0]; } } void UAbilityTask_WaitForInteractableTargets::AimWithPlayerController(const AActor* InSourceActor, FCollisionQueryParams Params, const FVector& TraceStart, float MaxRange, FVector& OutTraceEnd, bool bIgnorePitch) const { if (!Ability) // Server and launching client only { return; } //@TODO: Bots? APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get(); check(PC); FVector ViewStart; FRotator ViewRot; PC->GetPlayerViewPoint(ViewStart, ViewRot); const FVector ViewDir = ViewRot.Vector(); FVector ViewEnd = ViewStart + (ViewDir * MaxRange); ClipCameraRayToAbilityRange(ViewStart, ViewDir, TraceStart, MaxRange, ViewEnd); FHitResult HitResult; LineTrace(HitResult, InSourceActor->GetWorld(), ViewStart, ViewEnd, TraceProfile.Name, Params); const bool bUseTraceResult = HitResult.bBlockingHit && (FVector::DistSquared(TraceStart, HitResult.Location) <= (MaxRange * MaxRange)); const FVector AdjustedEnd = (bUseTraceResult) ? HitResult.Location : ViewEnd; FVector AdjustedAimDir = (AdjustedEnd - TraceStart).GetSafeNormal(); if (AdjustedAimDir.IsZero()) { AdjustedAimDir = ViewDir; } if (!bTraceAffectsAimPitch && bUseTraceResult) { FVector OriginalAimDir = (ViewEnd - TraceStart).GetSafeNormal(); if (!OriginalAimDir.IsZero()) { // Convert to angles and use original pitch const FRotator OriginalAimRot = OriginalAimDir.Rotation(); FRotator AdjustedAimRot = AdjustedAimDir.Rotation(); AdjustedAimRot.Pitch = OriginalAimRot.Pitch; AdjustedAimDir = AdjustedAimRot.Vector(); } } OutTraceEnd = TraceStart + (AdjustedAimDir * MaxRange); } bool UAbilityTask_WaitForInteractableTargets::ClipCameraRayToAbilityRange(FVector CameraLocation, FVector CameraDirection, FVector AbilityCenter, float AbilityRange, FVector& ClippedPosition) { FVector CameraToCenter = AbilityCenter - CameraLocation; float DotToCenter = FVector::DotProduct(CameraToCenter, CameraDirection); if (DotToCenter >= 0) //If this fails, we're pointed away from the center, but we might be inside the sphere and able to find a good exit point. { float DistanceSquared = CameraToCenter.SizeSquared() - (DotToCenter * DotToCenter); float RadiusSquared = (AbilityRange * AbilityRange); if (DistanceSquared <= RadiusSquared) { float DistanceFromCamera = FMath::Sqrt(RadiusSquared - DistanceSquared); float DistanceAlongRay = DotToCenter + DistanceFromCamera; //Subtracting instead of adding will get the other intersection point ClippedPosition = CameraLocation + (DistanceAlongRay * CameraDirection); //Cam aim point clipped to range sphere return true; } } return false; } void UAbilityTask_WaitForInteractableTargets::UpdateInteractableOptions(const FInteractionQuery& InteractQuery, const TArray>& InteractableTargets) { TArray NewOptions; for (const TScriptInterface& InteractiveTarget : InteractableTargets) { TArray TempOptions; FInteractionOptionBuilder InteractionBuilder(InteractiveTarget, TempOptions); InteractiveTarget->GatherInteractionOptions(InteractQuery, InteractionBuilder); for (FInteractionOption& Option : TempOptions) { FGameplayAbilitySpec* InteractionAbilitySpec = nullptr; // if there is a handle an a target ability system, we're triggering the ability on the target. if (Option.TargetAbilitySystem && Option.TargetInteractionAbilityHandle.IsValid()) { // Find the spec InteractionAbilitySpec = Option.TargetAbilitySystem->FindAbilitySpecFromHandle(Option.TargetInteractionAbilityHandle); } // If there's an interaction ability then we're activating it on ourselves. else if (Option.InteractionAbilityToGrant) { // Find the spec InteractionAbilitySpec = AbilitySystemComponent->FindAbilitySpecFromClass(Option.InteractionAbilityToGrant); if (InteractionAbilitySpec) { // update the option Option.TargetAbilitySystem = AbilitySystemComponent.Get(); Option.TargetInteractionAbilityHandle = InteractionAbilitySpec->Handle; } } if (InteractionAbilitySpec) { // Filter any options that we can't activate right now for whatever reason. if (InteractionAbilitySpec->Ability->CanActivateAbility(InteractionAbilitySpec->Handle, AbilitySystemComponent->AbilityActorInfo.Get())) { NewOptions.Add(Option); } } } } bool bOptionsChanged = false; if (NewOptions.Num() == CurrentOptions.Num()) { NewOptions.Sort(); for (int OptionIndex = 0; OptionIndex < NewOptions.Num(); OptionIndex++) { const FInteractionOption& NewOption = NewOptions[OptionIndex]; const FInteractionOption& CurrentOption = CurrentOptions[OptionIndex]; if (NewOption != CurrentOption) { bOptionsChanged = true; break; } } } else { bOptionsChanged = true; } if (bOptionsChanged) { CurrentOptions = NewOptions; InteractableObjectsChanged.Broadcast(CurrentOptions); } }