// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "MovementSplineActor.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "SonicMovementComponent.generated.h"

class ASonicCharacterComponent;

/**
 * Custom movement component for Sonic character
 */
UCLASS()
class SONICUNLEASHEDREMAKE_API USonicMovementComponent : public UCharacterMovementComponent
{
    GENERATED_BODY()
    
public:
    USonicMovementComponent();

    void CrouchMovementHandler();
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
    virtual void InitializeComponent() override;
    virtual void BeginPlay() override;
    void OnLanded();

    // Movement functions
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void MoveForward(float Value);

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void MoveRight(float Value);

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void Jump();

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    float GetForwardSpeed() const;
    
    

    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void LaunchSonic(float Speed, FVector Direction, float OutOfControlTime, float KeepVelocityTime,
        bool bAlignToMovementDirection, float HomingCooldown, bool bCanHomingAttackAfterLaunch = true,
        bool bCanAirDashAfterLaunch = true, bool bMaintainMomentum = false);
    

    // Control functions
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
    void GoOutOfControl(float Duration);

    void DoStomp();
    void DoUnleashedStomp();
    void DoGenerationsStomp();
    void DoCrouchedMovement();
    void DoSlide();
    void StopSlide();
    bool HighEnoughToStomp();
    bool IsSonicSliding();
    bool IsSonicCrouching();
    bool IsSonicStomping();
    //crouch stuff
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void RightFaceButtonPressed();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void RightFaceButtonReleased();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void EnableStomp();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        void DisableStomp();
    UFUNCTION(BlueprintCallable, Category = "Sonic Movement")
        bool IsStompEnabled();

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineInfluenceSpeedThreshold;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineInfluenceRadius;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement")
    float SplineNudgingForce;

    AMovementSplineActor* CurrentSplineActor;
    void HandleSplines();

protected:
    // Helper functions
    bool bCanStomp;
    
    float CalculateLateralMovementScale(float Speed) const;
    void ApplyLateralMovement();
    void DisableCharacterControls();
    void EnableCharacterControls();
    void UpdateCharacterRotation(float DeltaTime);

    // Timer handle
    FTimerHandle OutOfControlTimerHandle;

    // Character reference
    UPROPERTY()
    ASonicCharacterComponent* SonicCharacter;

    // Movement properties
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MaxBoostSpeed;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float BoostAcceleration;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float LateralMovementScale;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinLateralMovementScale;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinTurnRadius;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MaxTurnRadius;

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Sonic Movement")
    bool bIsOutOfControl;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float RotationInterpSpeed;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Sonic Movement", meta = (ClampMin = "0"))
    float MinSpeedForMaxTurnRadius;

    // Generations stomp will stop all movement and move you down
    // Unleashed stomp will keep your current momentum so send you down and still move you forward
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stomp", meta = (ClampMin = "0"))
        bool UnleashedStomp;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stomp", meta = (ClampMin = "0"))
        float StompPower;
    

};

// Fill out your copyright notice in the Description page of Project Settings.

#include "Sonic/SonicMovementComponent.h"
#include "Sonic/ESonicCharacterState.h"
#include "Sonic/HelperFunctions.h"
#include "Sonic/SonicCharacterComponent.h"
#include "GameFramework/PlayerController.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet/KismetMathLibrary.h"

USonicMovementComponent::USonicMovementComponent()
{
    MaxBoostSpeed = 9000.0f;
    BoostAcceleration = 2000.0f;
    MaxWalkSpeed = 6000.0f;
    MinLateralMovementScale = 0.1f;
    LateralMovementScale = 1.0f;
    bIsOutOfControl = false;
    MinTurnRadius = 10.0f;
    MaxTurnRadius = 100.0f;
    RotationInterpSpeed = 10.0f;
    MinSpeedForMaxTurnRadius = 5000.0f;
    SplineInfluenceSpeedThreshold = 5000.0f;
    SplineInfluenceRadius = 750.0f;
    SplineNudgingForce = 0.2f;
}

void USonicMovementComponent::CrouchMovementHandler()
{
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Sliding)
    {
        if (SonicCharacter->GetVelocity().Size() < 1)
        {
            StopSlide();
            return;
        }
        if (IsFalling()) StopSlide();
    }
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch)
    {
        if (SonicCharacter->GetVelocity().Size() > 1)
        {
            SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
        }
    }
}

void USonicMovementComponent::InitializeComponent()
{
    Super::InitializeComponent();

    // Initialize Sonic-specific movement parameters
    GravityScale = 4.0f;
    MaxAcceleration = 1000.0f;
    BrakingFrictionFactor = 0.05f;
    SetWalkableFloorAngle(60.0f);
    MaxWalkSpeed = 6000.0f;
    GroundFriction = 8.75f;
    JumpZVelocity = 800.0f;
    AirControl = 0.8f;
    BrakingDecelerationFalling = 0.2f;
    AirControlBoostMultiplier = 1.0f;
    FallingLateralFriction = 4.0f;
    MaxFlySpeed = 7000.0f;
    BrakingDecelerationFlying = 1750.0f;
    MaxCustomMovementSpeed = 9000.0f;
    RotationRate = FRotator(0.0f, 540.0f, 0.0f);
    bCanStomp = true;
    UnleashedStomp = true;
    StompPower = 2000.0f;
}

void USonicMovementComponent::BeginPlay()
{
    Super::BeginPlay();
    SonicCharacter = Cast<ASonicCharacterComponent>(GetOwner());
}


void USonicMovementComponent::OnLanded()
{
    SonicCharacter->BoostComponent->EnableAirBoosting();
    SonicCharacter->HomingAttackComponent->AirDashEnable();
    EnableStomp();
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::WallJumping || SonicCharacter->CurrentState == EFEeSonicCharacterState::Stomping)
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    
}

void USonicMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
    ApplyLateralMovement();
    UpdateCharacterRotation(DeltaTime);
    CrouchMovementHandler();
    HandleSplines();
}

//Calculate the lateral movement scale based on current speed, this will limit the influence of lateral input.
float USonicMovementComponent::CalculateLateralMovementScale(float Speed) const
{
    float MaxSpeed = MaxWalkSpeed;
    float Scale = 1.0f - (Speed / MaxSpeed);
    return FMath::Clamp(Scale, MinLateralMovementScale, 1.0f);
}

void USonicMovementComponent::ApplyLateralMovement()
{
    if (SonicCharacter)
    {
        float CurrentSpeed = SonicCharacter->GetVelocity().Size2D();
        LateralMovementScale = CalculateLateralMovementScale(CurrentSpeed);
    }
}

void USonicMovementComponent::MoveForward(float Value)
{
    if (!SonicCharacter || bIsOutOfControl || !SonicCharacter->bCanMove) return;
    if (IsSonicSliding() || IsSonicCrouching() || IsSonicStomping()) Value = 0;
    const FRotator Rotation = SonicCharacter->SonicCameraComponent->GetCurrentlyActiveCamera()->GetActorRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
    SonicCharacter->AddMovementInput(Direction, Value);
}

void USonicMovementComponent::MoveRight(float Value)
{
    if (!SonicCharacter || bIsOutOfControl || !SonicCharacter->bCanMove) return;
    if (IsSonicSliding() || IsSonicCrouching()) Value = 0;
    const FRotator Rotation = SonicCharacter->SonicCameraComponent->GetCurrentlyActiveCamera()->GetActorRotation();
    const FRotator YawRotation(0, Rotation.Yaw, 0);
    const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
    SonicCharacter->AddMovementInput(Direction, Value);
}

void USonicMovementComponent::UpdateCharacterRotation(float DeltaTime)
{
    if (!SonicCharacter) return;

    FVector CurrentVelocity = SonicCharacter->GetVelocity();
    float Speed = CurrentVelocity.Size2D();

    if (Speed > 0.1f)
    {
        // Calculate the target rotation based on velocity
        FRotator TargetRotation = CurrentVelocity.Rotation();

        // Calculate the turn radius based on speed
        float SpeedRatio = FMath::Clamp(Speed / MinSpeedForMaxTurnRadius, 0.0f, 1.0f);
        float CurrentTurnRadius = FMath::Lerp(MinTurnRadius, MaxTurnRadius, SpeedRatio);

        // Adjust the rotation rate based on the turn radius
        float CurrentRotationRate = 360.0f / (2 * PI * CurrentTurnRadius);
        RotationRate = FRotator(0.0f, CurrentRotationRate, 0.0f);

        // Interpolate the current rotation to the target rotation
        FRotator NewRotation = FMath::RInterpTo(SonicCharacter->GetActorRotation(), TargetRotation, DeltaTime, RotationInterpSpeed);

        // Set the new rotation
        SonicCharacter->SetActorRotation(NewRotation);
    }
}

void USonicMovementComponent::GoOutOfControl(float Duration)
{
    if (Duration <= 0.0f) return;

    bIsOutOfControl = true; 
    DisableCharacterControls();
    GetWorld()->GetTimerManager().SetTimer(OutOfControlTimerHandle, this, &USonicMovementComponent::EnableCharacterControls, Duration, false);
}

void USonicMovementComponent::DoStomp()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Stomping;
    DisableStomp();
    UnleashedStomp ? DoUnleashedStomp() : DoGenerationsStomp();
}


void USonicMovementComponent::DoUnleashedStomp()
{
    Velocity = SonicCharacter->GetVelocity()/2;
    FVector StompDirection = FVector(0.0f, 0.0f, -1.0f) * StompPower;
    AddImpulse(StompDirection, true);
}

void USonicMovementComponent::DoGenerationsStomp()
{
    Velocity = FVector::ZeroVector;
    FVector StompDirection = FVector(0.0f, 0.0f, -1.0f) * StompPower;
    AddImpulse(StompDirection, true);
}

void USonicMovementComponent::DoCrouchedMovement()
{
    if (!SonicCharacter) return;
    if (SonicCharacter->GetVelocity().Size() > 1)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("DoSlide"));
        DoSlide();
    }
    else
    {
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Crouch;
    }
}

void USonicMovementComponent::DoSlide()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Sliding;
}

void USonicMovementComponent::StopSlide()
{
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
}

bool USonicMovementComponent::HighEnoughToStomp()
{
    float searchDistance = 1000.0f;
    float distance = HelperFunctions::GetDistanceFromGround(GetWorld(), SonicCharacter->GetActorLocation(), searchDistance);
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("Distance from ground: %f"), distance));
    return distance > 30.0f;
}

bool USonicMovementComponent::IsSonicSliding()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Sliding;
}

bool USonicMovementComponent::IsSonicCrouching()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch;
}

bool USonicMovementComponent::IsSonicStomping()
{
    return SonicCharacter->CurrentState == EFEeSonicCharacterState::Stomping;
}


void USonicMovementComponent::RightFaceButtonPressed()
{
    if (!SonicCharacter) return;
    if (IsFalling() && IsStompEnabled() && HighEnoughToStomp())
    {
        DoStomp();
        return;
    }
    if (!IsFalling())
        DoCrouchedMovement();
}

void USonicMovementComponent::RightFaceButtonReleased()
{
    if (!SonicCharacter) return;
    if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Crouch)
    {
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    }
}

void USonicMovementComponent::EnableStomp()
{
    bCanStomp = true;
}

void USonicMovementComponent::DisableStomp()
{
    bCanStomp = false;
}

bool USonicMovementComponent::IsStompEnabled()
{
    return bCanStomp;
}

void USonicMovementComponent::HandleSplines()
{
    UWorld* World = GetWorld();
    if (!World)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("World is null!"));
        return;
    }

    TArray<AActor*> FoundActors;
    UGameplayStatics::GetAllActorsOfClass(World, AMovementSplineActor::StaticClass(), FoundActors);
    FVector PlayerLocation = SonicCharacter->GetActorLocation();
    float ClosestDistance = FLT_MAX;
    FVector DesiredFindLocation = PlayerLocation + (SonicCharacter->GetActorForwardVector() * 200.0f);
    if (FoundActors.Num() == 0)
    {
        CurrentSplineActor = nullptr;
        return;
    }
    for (AActor* FoundActor : FoundActors)
    {
        AMovementSplineActor* SplineActor = Cast<AMovementSplineActor>(FoundActor);
        if (SplineActor)
        {
            FVector SplineLocation = SplineActor->SplineComponent->FindLocationClosestToWorldLocation(DesiredFindLocation, ESplineCoordinateSpace::World);
            float Distance = FVector::Dist(PlayerLocation, SplineLocation);
            if (Distance < ClosestDistance)
            {
                ClosestDistance = Distance;
                CurrentSplineActor = SplineActor;
            }
        }
    }
    // we need to get the forward input to check if the user is holding the stick
    float forwardInput = SonicCharacter->GetInputAxisValue("MoveForward");
    float rightInput = SonicCharacter->GetInputAxisValue("MoveRight");
    float totalInput = FMath::Abs(forwardInput) + FMath::Abs(rightInput);
    
    if (totalInput < 0.1f && !SonicCharacter->BoostComponent->IsBoosting())
    {
        CurrentSplineActor = nullptr;
    }
    if (CurrentSplineActor && SonicCharacter->CurrentState == EFEeSonicCharacterState::MovingOnGround)
    {
        float ForwardDistance = 200.0f;
        FVector ForwardVector = SonicCharacter->GetActorForwardVector() * ForwardDistance;
        FVector DesiredLocation = PlayerLocation + ForwardVector;
        FVector Location = CurrentSplineActor->SplineComponent->FindLocationClosestToWorldLocation(DesiredLocation, ESplineCoordinateSpace::World);
        FRotator DesiredRotation = (Location - PlayerLocation).GetSafeNormal().Rotation();
        DesiredRotation.Pitch = 0.0f;
        SonicCharacter->FancyArrow->SetWorldRotation(DesiredRotation);

        float DotProduct = FVector::DotProduct(SonicCharacter->GetActorForwardVector(), (Location - PlayerLocation).GetSafeNormal());
        if (DotProduct < 0.0f)
        {
            CurrentSplineActor = nullptr;
            return;
        }
        FVector DesiredDirection = (Location - PlayerLocation).GetSafeNormal();
        float DistanceToSpline = FVector::Dist(SonicCharacter->GetActorLocation(), Location);
        float SpeedInfluence = FMath::Clamp(SonicCharacter->GetVelocity().Size() / SplineInfluenceSpeedThreshold, 0.0f, 1.0f );
        float SplineWeight = FMath::Clamp((1.0f - DistanceToSpline / SplineInfluenceRadius) * SpeedInfluence, 0.0f, 1.0f);

        FVector MovementDirection = SonicCharacter->GetActorForwardVector();
        FVector BlendedDirection = FMath::Lerp(MovementDirection, DesiredDirection, SplineWeight).GetSafeNormal();
        
        SonicCharacter->AddMovementInput(BlendedDirection, SplineNudgingForce * SplineWeight);
        
    }
}

void USonicMovementComponent::DisableCharacterControls()
{
    if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
    {
        SonicCharacter->DisableInput(PlayerController);
    }
}

void USonicMovementComponent::EnableCharacterControls()
{
    if (APlayerController* PlayerController = GetWorld()->GetFirstPlayerController())
    {
        SonicCharacter->EnableInput(PlayerController);
    }
    bIsOutOfControl = false;
}

void USonicMovementComponent::Jump()
{
    if (!SonicCharacter) return;
    const FVector JumpDirection = SonicCharacter->GetActorUpVector() * JumpZVelocity;
    AddImpulse(JumpDirection, true);
    SonicCharacter->CurrentState = EFEeSonicCharacterState::Jumping;
}

float USonicMovementComponent::GetForwardSpeed() const
{
    FVector Velo = Velocity;
    Velo.Z = 0.0f;
    return Velo.Size();
}

void USonicMovementComponent::LaunchSonic(float Speed, FVector Direction, float OutOfControlTime,
                                          float KeepVelocityTime, bool bAlignToMovementDirection, float HomingCooldown, 
                                          bool bCanHomingAttackAfterLaunch, bool bCanAirDashAfterLaunch, bool bMaintainMomentum)
{
    if (!SonicCharacter)
    {
        UE_LOG(LogTemp, Error, TEXT("SonicMovementComponent: SonicCharacter is not set!"));
        return;
    }

    const FVector LaunchVelocity = Direction * Speed;
    if (LaunchVelocity.Z > 0.0f)
    {
        SetMovementMode(MOVE_Falling);
        SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
    }

    if (!bMaintainMomentum)
    {
        Velocity = FVector::ZeroVector;
    }
    Velocity += LaunchVelocity;

    if (bAlignToMovementDirection)
    {
        FRotator LaunchRotation = LaunchVelocity.Rotation();
        LaunchRotation.Pitch = SonicCharacter->GetActorRotation().Pitch;
        LaunchRotation.Roll = SonicCharacter->GetActorRotation().Roll;
        SonicCharacter->SetActorRotation(LaunchRotation);
    }

    // Handle component-specific logic
    if (bCanAirDashAfterLaunch)
    {
        SonicCharacter->HomingAttackComponent->AirDashEnable();
    }
    if (bCanHomingAttackAfterLaunch)
    {
        SonicCharacter->HomingAttackComponent->HomingAttackEnable();
    }
    if (HomingCooldown > 0.0f)
    {
        SonicCharacter->HomingAttackComponent->HomingAttackCooldownEnable(HomingCooldown);
    }
    if (OutOfControlTime > 0.0f)
    {
        GoOutOfControl(OutOfControlTime);
    }

    // Debug message
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, FString::Printf(TEXT("LaunchSonic: Speed=%f, Direction=(%f, %f, %f)"), Speed, Direction.X, Direction.Y, Direction.Z));
}

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "BoostComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SONICUNLEASHEDREMAKE_API UBoostComponent : public UActorComponent
{
	class ASonicCharacterComponent* SonicCharacter;
	GENERATED_BODY()
	//these are the delegates that we will use to play the boost sound and particles
	//these basically are the in blueprint events
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnBoostEvent);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnAirBoostEvent);
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FBoostingEvent); // this one will tick every tick that we are boosting
	DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnStopBoostEvent);

	//events to be accessed in the Blueprints
	UPROPERTY(BlueprintAssignable, Category = "Boost")
		FOnBoostEvent OnBoostEvent;
	UPROPERTY(BlueprintAssignable, Category = "Boost")
		FOnAirBoostEvent OnAirBoostEvent;
	UPROPERTY(BlueprintAssignable, Category = "Boost")
		FOnStopBoostEvent OnStopBoostEvent;
	UPROPERTY(BlueprintAssignable, Category = "Boost")
		FBoostingEvent BoostingEvent;

public:	
	// Sets default values for this component's properties
	UBoostComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Boost")
		float BoostPower;

	UFUNCTION(BlueprintCallable, Category = "Boost")
		void BoostPressed();
	UFUNCTION(BlueprintCallable, Category = "Boost")
		void BoostReleased();
	
	void BoostHandler();
	void AirBoosting();
	UFUNCTION(BlueprintCallable, Category = "Boost")
		void EndAirBoostAnimation();
	UFUNCTION(BlueprintCallable, Category = "Boost")
		bool IsAllowedToBoost() const;
	
	UFUNCTION(BlueprintCallable, Category = "Boost")
		void EnableAirBoosting();
	UFUNCTION(BlueprintCallable, Category = "Boost")
		void DisableAirBoosting();
	
	UFUNCTION(BlueprintCallable, Category = "Boost")
	bool IsBoosting() const { return bIsBoosting; }
	float BoostExtraSpeedConstant;

	FTimerHandle AirBoostAnimationTimer;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Boost")
		bool bIsBoosting;
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Boost")
		bool bHasAirBoosted; //you will never actually be in the state of airboosting, because it only triggers once

};

// Fill out your copyright notice in the Description page of Project Settings.


#include "Sonic/BoostComponent.h"

#include "GameFramework/CharacterMovementComponent.h"
#include "Runtime/SymsLib/syms/core/generated/syms_meta_serial_ext.h"
#include "Sonic/SonicCharacterComponent.h"

// Sets default values for this component's properties
UBoostComponent::UBoostComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	PrimaryComponentTick.bCanEverTick = true;
	BoostPower = 100.0f;
	BoostExtraSpeedConstant = 2000.0f;
	bIsBoosting = false; //this is a toggle boolean
	bHasAirBoosted = false;
	
}


// Called when the game starts
void UBoostComponent::BeginPlay()
{
	Super::BeginPlay();
	SonicCharacter = Cast<ASonicCharacterComponent>(GetOwner());
}


// Called every frame
void UBoostComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	BoostHandler();
}

void UBoostComponent::BoostPressed()
{
	//we play sound and particles and camera shake in the blueprint
	//todo check if we can even boost
	//todo add boost meter implementation

	//only boost if we are not currently boosting, to prevent double inputs
	if (bIsBoosting || bHasAirBoosted) return;
	
	if (SonicCharacter->SonicMovement->IsFalling())
	{
		AirBoosting();
		return;
	}
	OnBoostEvent.Broadcast();
	bIsBoosting = true;
	const float MissingSpeedFromMax = SonicCharacter->SonicMovement->GetMaxSpeed() - SonicCharacter->SonicMovement->Velocity.Size();
	const FVector BoostDirection = SonicCharacter->GetActorForwardVector() * MissingSpeedFromMax;
	SonicCharacter->SonicMovement->Velocity += BoostDirection;
	SonicCharacter->SonicMovement->MaxWalkSpeed = SonicCharacter->SonicMovement->GetMaxSpeed() + BoostExtraSpeedConstant;
	SonicCharacter->SonicMovement->StopSlide();
}


void UBoostComponent::BoostHandler()
{
	if (!bIsBoosting)
		return;
	if (!IsAllowedToBoost())
	{
		BoostReleased(); // force release boost if not allowed anymore
		return;
	}
	//boost!
	SonicCharacter->AddMovementInput(SonicCharacter->GetActorForwardVector(), 2.0f);

}

void UBoostComponent::AirBoosting()
{
	bHasAirBoosted = true;
	OnAirBoostEvent.Broadcast();
	const float MissingSpeedFromMax = SonicCharacter->GetCharacterMovement()->GetMaxSpeed() - SonicCharacter->GetCharacterMovement()->Velocity.Size();
	const FVector BoostDirection = SonicCharacter->GetActorForwardVector() * MissingSpeedFromMax;
	SonicCharacter->GetCharacterMovement()->Velocity += BoostDirection;
	//this sets the state to airboosting for 0.3 seconds, so we can play the animation
	SonicCharacter->CurrentState = EFEeSonicCharacterState::AirBoosting;
	GetWorld()->GetTimerManager().SetTimer(AirBoostAnimationTimer, this, &UBoostComponent::EndAirBoostAnimation, 0.3f, false);
	SonicCharacter->HomingAttackComponent->AirDashDisable();
	if (SonicCharacter->CurrentState == EFEeSonicCharacterState::Jumping)
	{
		SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
	}


}

void UBoostComponent::EndAirBoostAnimation()
{
	if (SonicCharacter->CurrentState == EFEeSonicCharacterState::AirBoosting)
	{
		SonicCharacter->CurrentState = EFEeSonicCharacterState::Falling;
	}

}


bool UBoostComponent::IsAllowedToBoost() const
{
	//check if we are in the air
	const bool AirCheck = !SonicCharacter->GetCharacterMovement()->IsFalling();
	const bool SpeedCheck = SonicCharacter->GetCharacterMovement()->Velocity.Size() > 500.0f; // todo 500 is the boosting min speed, make it a variable
	return AirCheck && SpeedCheck;
}


void UBoostComponent::BoostReleased()
{
	if (!bIsBoosting)
	{
		return;
	}
	bIsBoosting = false;
	//reset the walk speed
	SonicCharacter->SonicMovement->MaxWalkSpeed = SonicCharacter->SonicMovement->GetMaxSpeed() - BoostExtraSpeedConstant;
	OnStopBoostEvent.Broadcast();
}

inline void UBoostComponent::EnableAirBoosting()
{
	bHasAirBoosted = false;
}

inline void UBoostComponent::DisableAirBoosting()
{
	bHasAirBoosted = true;
}

Okay so, right now the boost is just a movementInput. but i dont know if this is the best way of doing it, and especially because the movement of the splines its like a bit weird when i boost sonic doesnt follow the spline all that well, 

maybe we can rethink the spline following system, Obviously i dont want the character to just snap on that spline, the MovementSplineActor also has a "Influence Rate" float, maybe we can also implement that, please help me out