// 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));
}


Right now sonic can only run on the floor, but i want it so, once you run fast enough, (around 500 units) you can start running on walls and if you maintain your speed you can keep running on the walls, or even roofs.
but i only want this to happen if it happens smoothly, simply running and jumping against a wall should NOT work, only if the ground allows for smooth movement all around. 
So if the ground allows for a smooth way to go from the ground to the wall and then wall to roof in a smooth manner, then it should work. The camera should always stay upright.
also i want sonics mesh to correctly rotate based on if hes running on the wall and if hes on sloped floors ect, can you think you can properly implement this. if so, do it