Advertisement
Guest User

MTD_FoeState_MovingAroundBlockade.cpp

a guest
Oct 11th, 2023
166
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 16.76 KB | None | 0 0
  1. #include "Gameplay/Foes/AI/States/MTD_FoeState_MovingAroundBlockade.h"
  2.  
  3. #include "Character/MTD_CharacterWithAbilities.h"
  4. #include "Components/ShapeComponent.h"
  5. #include "FiniteStateMachine/FiniteStateMachine.h"
  6. #include "Gameplay/Foes/AI/Components/MTD_PathFollowingComponent.h"
  7. #include "Gameplay/Foes/AI/States/MTD_FoeState_Seeking.h"
  8. #include "Gameplay/Foes/MTD_FoeController.h"
  9. #include "Kismet/KismetMathLibrary.h"
  10. #include "NativeGameplayTags.h"
  11. #include "Physics/MTD_CollisionChannels.h"
  12. #include "Utilities/MTD_GameplayStatics.h"
  13. #include "Utilities/MTD_Log.h"
  14.  
  15. DEFINE_LOG_CATEGORY(MTD_LogFoe_MoveAroundBlockade);
  16. UE_DEFINE_GAMEPLAY_TAG(TAG_StateMachine_Label_MoveAround, "StateMachine.Label.MoveAround");
  17.  
  18. #define DRAW_DEBUG
  19. #define DRAW_TIME 2.f
  20. #define DRAW_THICKNESS 1.f
  21. #ifdef DRAW_DEBUG
  22.     #define DRAW_DEBUG_TYPE EDrawDebugTrace::ForDuration
  23. #elif
  24.     #define DRAW_DEBUG_TYPE EDrawDebugTrace::None
  25. #endif
  26.  
  27. UMTD_FoeState_MovingAroundBlockade::UMTD_FoeState_MovingAroundBlockade()
  28. {
  29.     StateDataClass = UMTD_FoeStateData_MovingAroundBlockade::StaticClass();
  30.  
  31.     InitialLabelsToRegister.Add(TAG_StateMachine_Label_MoveAround,
  32.         FLabelSignature::CreateUObject(this, &ThisClass::MoveAroundBlockade));
  33. }
  34.  
  35. bool UMTD_FoeState_MovingAroundBlockade::MoveAroundBlockade(UFiniteStateMachine* StateMachineContext,
  36.     AActor* BlockadeActor, float BlockadeWidth, FVector HitNormal, bool bSkipBlockingCheck /*false*/)
  37. {
  38.     if (!IsValid(BlockadeActor))
  39.     {
  40.         return false;
  41.     }
  42.  
  43.     auto* StateData = StateMachineContext->GetStateDataChecked<UMTD_FoeStateData_MovingAroundBlockade>(StaticClass());
  44.     const AActor* Avatar = StateMachineContext->GetAvatar();
  45.     if (!IsValid(Avatar))
  46.     {
  47.         return false;
  48.     }
  49.  
  50.     const FVector Velocity = Avatar->GetVelocity();
  51.     const float SpeedSq = Velocity.SquaredLength();
  52.     const float MoveAroundSomethingMaximumSpeedSq = FMath::Square(StateData->MoveAroundSomethingMaximumSpeed);
  53.  
  54.     // Don't try to move around blockade if we're moving at a high enough speed still
  55.     if (SpeedSq > MoveAroundSomethingMaximumSpeedSq)
  56.     {
  57.         return false;
  58.     }
  59.  
  60.     // Cheap hack to prevent enemies from moving around each other again as they all try to move around each other
  61.     if (StateData->bIgnoreMoveAroundBlockade)
  62.     {
  63.         return false;
  64.     }
  65.  
  66.     // if (!bReachedInitialTarget)
  67.     // {
  68.     //  return false;
  69.     // }
  70.  
  71.     // bInvicible?
  72.  
  73.     // Only move-around-blockade when seeking or moving around a blockade
  74.     if (!StateMachineContext->IsInState(UMTD_FoeState_Seeking::StaticClass()) &&
  75.         !StateMachineContext->IsInState(UMTD_FoeState_MovingAroundBlockade::StaticClass()))
  76.     {
  77.         return false;
  78.     }
  79.  
  80.     if (UMTD_GameplayStatics::TimeSince(StateMachineContext, StateData->LastBlockadeCheckTime) < 0.5f)
  81.     {
  82.         MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Target [%s] is irrelevant as "
  83.             "we've tried to move around very recently [%f].", *BlockadeActor->GetName(),
  84.             UMTD_GameplayStatics::TimeSince(StateMachineContext, StateData->LastBlockadeCheckTime));
  85.         return false;
  86.     }
  87.  
  88.     const auto* GlobalStateData = StateMachineContext->GetStateDataChecked<UMTD_GlobalFoeStateData>(
  89.         UMTD_FoeState_Global::StaticClass());
  90.  
  91.     if (GlobalStateData->Target == BlockadeActor)
  92.     {
  93.         MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Target [%s] is the same as the "
  94.             "current target.", *BlockadeActor->GetName());
  95.         return false;
  96.     }
  97.  
  98.     if (BlockadeActor == StateData->LastMovingAroundBlockadeActor &&
  99.         UMTD_GameplayStatics::TimeSince(StateMachineContext, StateData->LastMovingAroundBlockadeTime) < 2.6f)
  100.     {
  101.         MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Target [%s] is a very recent "
  102.             "[%f] try.", *BlockadeActor->GetName(),
  103.             UMTD_GameplayStatics::TimeSince(StateMachineContext, StateData->LastMovingAroundBlockadeTime));
  104.         return false;
  105.     }
  106.  
  107.     StateData->LastBlockadeCheckTime = UMTD_GameplayStatics::GetTime(StateMachineContext);
  108.  
  109.     // If the blockade itself doesn't specify how wide it is, guess based on its collision component
  110.     if (BlockadeWidth == 0.f)
  111.     {
  112.         const auto* CollisionComponent = BlockadeActor->FindComponentByTag<UShapeComponent>(CollisionComponentTag);
  113.         if (!IsValid(CollisionComponent))
  114.         {
  115.             MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Target [%s] does not have "
  116.                 "a shape component with tag [%s].", *BlockadeActor->GetName(), *CollisionComponentTag.ToString());
  117.             return false;
  118.         }
  119.  
  120.         BlockadeWidth = CollisionComponent->Bounds.SphereRadius;
  121.     }
  122.  
  123.     const FRotator AvatarRotator = Avatar->GetActorRotation();
  124.     const FVector AvatarRotationVector = AvatarRotator.Vector();
  125.     const float Dot = FVector::DotProduct(HitNormal.GetSafeNormal2D(), AvatarRotationVector.GetSafeNormal2D());
  126.     if (Dot > 0.f)
  127.     {
  128.         MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Target [%s] is behind us.",
  129.             *BlockadeActor->GetName());
  130.         return false;
  131.     }
  132.  
  133.     const FVector OurPosition = Avatar->GetActorLocation();
  134.     const float AngleBetween = 180.f - UKismetMathLibrary::DegAcos(Dot);
  135.     const FVector RotatedHitNormal = HitNormal.RotateAngleAxis(AngleBetween, FVector::UpVector);
  136.     const FRotator HitNormalRotator = RotatedHitNormal.Rotation();
  137.  
  138.     const UWorld* World = StateMachineContext->GetWorld();
  139.     check(IsValid(World));
  140.  
  141.     // Only run the blockade-validity if specified to (do by default). If we don't want to do the checks, we probably
  142.     // just want to switch directions
  143.     if (!bSkipBlockingCheck)
  144.     {
  145.         // We are going to see if this blockade actually is in the way of our destination
  146.  
  147.         // Store our destination in a temporary variable for checking
  148.         const FVector TargetDesinationPosition = FVector::ZeroVector; // @todo MoveToTarget state
  149.         FVector Delta = FVector::ZeroVector;
  150.         if (!TargetDesinationPosition.IsZero())
  151.         {
  152.             Delta = TargetDesinationPosition - OurPosition;
  153.         }
  154.         else if (GlobalStateData->Target.IsValid())
  155.         {
  156.             Delta = GlobalStateData->Target->GetActorLocation() - OurPosition;
  157.         }
  158.  
  159.         FVector TraceThroughBlockadePosition = FVector::ZeroVector;
  160.         if (!Delta.IsZero())
  161.         {
  162.             TraceThroughBlockadePosition = Delta.GetSafeNormal() * BlockadeWidth * 1.5f + OurPosition;
  163.         }
  164.  
  165.         bool bFoundAnyTraceCheck = false;
  166.  
  167.         // Just in case we may not actually have a target destination
  168.         if (!TraceThroughBlockadePosition.IsZero())
  169.         {
  170. #ifdef DRAW_DEBUG
  171.             DrawDebugSphere(World, TraceThroughBlockadePosition, 30, 16, FColor::Red, false, DRAW_TIME, 0, DRAW_THICKNESS);
  172.             DrawDebugSphere(World, OurPosition, 30, 16, FColor::Green, false, DRAW_TIME, 0, DRAW_THICKNESS);
  173.             DrawDebugLine(World, OurPosition, TraceThroughBlockadePosition, FColor::White, false, DRAW_TIME, 0, DRAW_THICKNESS);
  174. #endif
  175.  
  176.             const auto* CollisionSettings = GetDefault<UCollisionProfile>();
  177.             const ETraceTypeQuery TraceChannel = CollisionSettings->ConvertToTraceType(ECC_Visibility);
  178.  
  179.             TArray<FHitResult> Hits;
  180.             UKismetSystemLibrary::BoxTraceMulti(Avatar, TraceThroughBlockadePosition, OurPosition,
  181.                 FVector(BlockadeWidth, BlockadeWidth, 0) / 2.f * 0.9f, FRotator::ZeroRotator, TraceChannel, false, {},
  182.                 DRAW_DEBUG_TYPE, OUT Hits, true, FLinearColor::Red, FLinearColor::Green, DRAW_TIME);
  183.  
  184.             // World->LineTraceMultiByObjectType(Hits, TraceThroughBlockadePosition, OurPosition, ObjectQueryParams,
  185.             //  CollisionQueryParams);
  186.  
  187.             // Check if the blockade is actually standing in the way of our target destination
  188.             for (const FHitResult& Hit : Hits)
  189.             {
  190.                 const AActor* HitActor = Hit.GetActor();
  191.                 MTDC_VVERBOSE(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Collided with blockade [%s] "
  192.                     "Trace checked [%s].", *BlockadeActor->GetName(), *HitActor->GetName());
  193.  
  194.                 if (HitActor == BlockadeActor)
  195.                 {
  196.                     bFoundAnyTraceCheck = true;
  197.                     break;
  198.                 }
  199.             }
  200.         }
  201.  
  202.         // If blockade does not appear to be in the way of our target destination, cancel moving around it
  203.         // It probably was just a side-glancing collision and not point to navigate around that
  204.         if (!bFoundAnyTraceCheck)
  205.         {
  206.             MTDC_LOG(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Ignoring... Collided with blockade [%s] but "
  207.                 "it wasn't in trace path towards our target.", *BlockadeActor->GetName());
  208.             return false;
  209.         }
  210.     }
  211.  
  212.     const UShapeComponent* CollisionComponent = Avatar->FindComponentByTag<UShapeComponent>(CollisionComponentTag);
  213.     if (!IsValid(CollisionComponent))
  214.     {
  215.         MTDC_WARN(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Cancelling... Our pawn [%s] lacks a collision "
  216.             "component with tag [%s].", *Avatar->GetName(), *CollisionComponentTag.ToString());
  217.         return false;
  218.     }
  219.  
  220.     const float OurWidth = CollisionComponent->Bounds.SphereRadius;
  221.     FVector Offset;
  222.  
  223.     // Start with a navigation offset that is sideways, so that we will be moving to the left or right of the blockade.
  224.     // MovingAroundBlockadeSide stores our current side (left or right) so that all subsequent blockade movements
  225.     // automatically keep going in the same direction
  226.     Offset.Y = StateData->MovingAroundBlockadeSide * (BlockadeWidth + OurWidth);
  227.  
  228.     // Push the offset a little backwards so we tend to navigate around the blockade
  229.     Offset.X = -50.f - OurWidth;
  230.  
  231.     const FVector BlockadePosition = BlockadeActor->GetActorLocation();
  232. #ifdef DRAW_DEBUG
  233.     DrawDebugLine(World, BlockadePosition, BlockadePosition + HitNormal * 150.f, FColor::Red, false, 2.f, 0, 1.f);
  234. #endif
  235.  
  236.     // Calculate the final move-around point as the blockade's location, plus our offset transformed by the rotation
  237.     // of the normal, so we effectively pick a side relative to our collision face's orientation
  238.     StateData->MovingAroundBlockadePoint = BlockadePosition + HitNormalRotator.RotateVector(Offset);
  239.  
  240.     // Ensure the point we're attempting to navigate to is at our height
  241.     StateData->MovingAroundBlockadePoint.Z = OurPosition.Z;
  242.  
  243. #ifdef DRAW_DEBUG
  244.     DrawDebugSphere(World, StateData->MovingAroundBlockadePoint, 15.f, 6, FColor::Cyan, false, 2.f, 0, 1.f);
  245. #endif
  246.  
  247.     // Check that the point we're going to try to navigate to around the blockade actually is a decent point --
  248.     // that it has ground underneath it; if it's not a valid point, then switch moving side
  249.     if (!CheckMoveAroundBlockadePoint(StateMachineContext, StateData->MovingAroundBlockadePoint))
  250.     {
  251.         // Store the new side so that subsequent blockade movements will also travel in it
  252.         StateData->MovingAroundBlockadeSide *= -1.f;
  253.  
  254.         // Switch the side direction of the offset
  255.         Offset.Y *= -1.f;
  256.  
  257.         // And transform that new point
  258.         StateData->MovingAroundBlockadePoint = BlockadePosition + HitNormalRotator.RotateVector(Offset);
  259.  
  260.         // Ensure the point we're attempting to navigate to is at our height
  261.         StateData->MovingAroundBlockadePoint.Z = OurPosition.Z;
  262.  
  263.         MTDC_LOG(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Failed going [%s] side, trying to go the other "
  264.             "one...", StateData->MovingAroundBlockadeSide == 1.f ? TEXT("Right") : TEXT("Left"));
  265.  
  266.         // Check the new-direction position as well just to be safe -- if this fails, we're just not going to do anything
  267.         // right now... hopefully an ally will bust through that blockade soon!
  268.         if(!CheckMoveAroundBlockadePoint(StateMachineContext, StateData->MovingAroundBlockadePoint))
  269.         {
  270.             // If we're here, then there were no valid points around the blocakde, so...
  271.             // This blockade is totally blocking us, so perhaps let's try to destroy it!
  272.             // @todo AggroEntries.Remove(0,AggroEntries.Length);
  273.             // @todo AddToAggro(DunDefTargetableInterface(blockadeActor),1.0);
  274.  
  275.             MTDC_LOG(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Failed final viability check.");
  276.             return false;
  277.         }
  278.     }
  279.  
  280.     MTDC_LOG(MTD_LogFoe_MoveAroundBlockade, StateMachineContext, "Moving around blockade...");
  281.  
  282.     // Store a reference to the blockade actor we're moving around, so that we don't attempt to move around it again
  283.     // while already moving around it
  284.     StateData->LastMovingAroundBlockadeActor = BlockadeActor;
  285.  
  286.     // Find an unobstructed point somewhere near to our move-around point (sets it into the movearound point)
  287.     // @todo    FindSpot(Pawn.CollisionComponent.Bounds.BoxExtent, MovingAroundBlockadePoint);
  288.  
  289.     StateData->LastBlockadeWidth = BlockadeWidth;
  290.     StateData->LastBlockadeHitNormal = HitNormal;
  291.  
  292.     // Finally, we're ready to enter or restart the MovingAroundBlockade state and attempt to head to our
  293.     // move-around point!
  294.     if (!bSkipBlockingCheck)
  295.     {
  296.         // If we did a full check, then begin a completely new blockade movement, starting at the beginning of the
  297.         // MovingAroundBlockade state code
  298.         StateData->LastMovingAroundBlockadeTime = UMTD_GameplayStatics::GetTime(StateMachineContext);
  299.         StateData->StartMovingAroundBlockadeLocation = OurPosition;
  300.  
  301.         // StateMachineContext->GotoState(StaticClass());
  302.         StateMachineContext->GotoState(StaticClass(), TAG_StateMachine_Label_MoveAround);
  303.     }
  304.     else
  305.     {
  306.         // If we did not do a full check (and were just intending to switch directions due to a time-out),
  307.         // then go to the MoveAround Label, which will not reset our time-out timers, so that we will still time-out
  308.         // of the overall state if it takes too long
  309.         StateMachineContext->GotoState(StaticClass(), TAG_StateMachine_Label_MoveAround);
  310.     }
  311.  
  312.     return true;
  313. }
  314.  
  315. bool UMTD_FoeState_MovingAroundBlockade::CheckMoveAroundBlockadePoint(const UFiniteStateMachine* StateMachineContext,
  316.     const FVector& MoveToPoint)
  317. {
  318.     const AActor* Avatar = StateMachineContext->GetAvatar();
  319.     check(IsValid(Avatar));
  320.  
  321.     const UShapeComponent* CollisionComponent = Avatar->FindComponentByTag<UShapeComponent>(
  322.         CollisionComponentTag);
  323.     check(IsValid(CollisionComponent));
  324.  
  325.     const FVector Offset = FVector::ZAxisVector * CollisionComponent->Bounds.SphereRadius * -2.f;
  326.  
  327.     FHitResult Hit;
  328.     const bool bHasGround = Avatar->GetWorld()->LineTraceSingleByChannel(OUT Hit, MoveToPoint, MoveToPoint + Offset,
  329.         ECC_Visibility);
  330.  
  331.     return bHasGround;
  332. }
  333.  
  334. void UMTD_FoeState_MovingAroundBlockade::Begin(TSubclassOf<UMachineState> PreviousState)
  335. {
  336.     Super::Begin(PreviousState);
  337.  
  338.     if (!StateData.IsValid())
  339.     {
  340.         StateData = StateMachine->GetStateDataChecked<UMTD_FoeStateData_MovingAroundBlockade>(StaticClass());
  341.     }
  342.  
  343.     auto* PathFolliwingComponent = Controller->GetComponentByClass<UMTD_PathFollowingComponent>();
  344.     PathFolliwingComponent->AddObstacle(StateData->LastMovingAroundBlockadeActor.Get());
  345.     PathFolliwingComponent->SetSteeringEnabled(true);
  346.  
  347.     FTimerManager& TimerManager = GetWorld()->GetTimerManager();
  348.     TimerManager.SetTimer(CancelMoveAroundTimer, this, &ThisClass::CancelMoveAround, 4.f, false);
  349.     TimerManager.SetTimer(TryOtherBlockadeDirectionTimer, this, &ThisClass::TryOtherBlockadeDirection, 0.6f, true);
  350. }
  351.  
  352. void UMTD_FoeState_MovingAroundBlockade::Tick(float DeltaSeconds)
  353. {
  354.     Super::Tick(DeltaSeconds);
  355.  
  356.     // Check if we don't appear to be moving or have not moved 1.5 seconds after entering this state,
  357.     // and if not, cancel the move around
  358.     if (TimeSince(StateData->LastMovingAroundBlockadeTime) > 1.5f)
  359.     {
  360.         const FVector OurPosition = Character->GetActorLocation();
  361.         const FVector DistanceVector = OurPosition - StateData->StartMovingAroundBlockadeLocation;
  362.         const float DistanceSq = DistanceVector.SquaredLength();
  363.         if (DistanceSq < 0.01f)
  364.         {
  365.             CancelMoveAround();
  366.         }
  367.         else
  368.         {
  369.             const FVector Velocity = Character->GetVelocity();
  370.             const float SpeedSq = Velocity.SquaredLength();
  371.             if (SpeedSq < 1.f)
  372.             {
  373.                 CancelMoveAround();
  374.             }
  375.         }
  376.     }
  377. }
  378.  
  379. void UMTD_FoeState_MovingAroundBlockade::End(TSubclassOf<UMachineState> NewState)
  380. {
  381.     CancelMoveAround();
  382.  
  383.     auto* PathFolliwingComponent = Controller->GetComponentByClass<UMTD_PathFollowingComponent>();
  384.     PathFolliwingComponent->RemoveObstacle(StateData->LastMovingAroundBlockadeActor.Get());
  385.     PathFolliwingComponent->SetSteeringEnabled(false);
  386.  
  387.     Super::End(NewState);
  388. }
  389.  
  390. void UMTD_FoeState_MovingAroundBlockade::MoveAroundBlockade(float DeltaSeconds)
  391. {
  392.     Controller->MoveToLocation(StateData->MovingAroundBlockadePoint, 50.f, false, false);
  393. }
  394.  
  395. void UMTD_FoeState_MovingAroundBlockade::CancelMoveAround()
  396. {
  397.     MTD_VERBOSE(MTD_LogFoe_MoveAroundBlockade, "Cancel move around.");
  398.  
  399.     FTimerManager& TimerManager = GetWorld()->GetTimerManager();
  400.     TimerManager.ClearTimer(CancelMoveAroundTimer);
  401.     TimerManager.ClearTimer(TryOtherBlockadeDirectionTimer);
  402.  
  403.     StateMachine->GotoState(UMTD_FoeState_Seeking::StaticClass());
  404. }
  405.  
  406. void UMTD_FoeState_MovingAroundBlockade::TryOtherBlockadeDirection()
  407. {
  408.     const FVector Velocity = Character->GetVelocity();
  409.     const float SpeedSq = Velocity.SquaredLength();
  410.     if (SpeedSq < 25.f * 25.f)
  411.     {
  412.         MTD_VERBOSE(MTD_LogFoe_MoveAroundBlockade, "Try other blockade direction.");
  413.  
  414.         StateData->MovingAroundBlockadeSide *= -1.f;
  415.         AActor* BlockadeActor = StateData->LastMovingAroundBlockadeActor.Get();
  416.         StateData->LastMovingAroundBlockadeActor.Reset();
  417.         MoveAroundBlockade(StateMachine.Get(), BlockadeActor, StateData->LastBlockadeWidth,
  418.             StateData->LastBlockadeHitNormal, true);
  419.     }
  420. }
  421.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement