UCustomMovementComponent::UCustomMovementComponent()
{
	// Initialization

	//Custom Movement Component

	GravityScale = 2.0f;
	bCanJump = true;
	JumpHeight = 300.0f;
	GroundHitToleranceDistance = 20.0f;
	SpeedBoostMultiplier = 2.0f;
	AirControlRatio = 0.5f;
	GravitySwitchDelay = 0.5f;
	bResetVelocityOnGravitySwitch = false;

	StandingVerticalOrientation = EVerticalOrientation::EVO_GravityDirection;
	FallingVerticalOrientation = EVerticalOrientation::EVO_GravityDirection;
	OrientationSettings = FOrientationSettings();

	DebugDrawType = EDrawDebugTrace::None;

	CustomGravityType = EGravityType::EGT_Default;
	CustomGravityInfo = FGravityInfo();
	PlanetActor = nullptr;

	SurfaceBasedGravityInfo = FGravityInfo();
	TraceShape = ETraceShape::ETS_Sphere;
	TraceChannel = ECollisionChannel::ECC_Pawn;
	TraceShapeScale = 0.75f;
	bUseCapsuleHit = false;

	bEnablePhysicsInteraction = false;
	HitForceFactor = 0.25f;
	bEnablePhysicsInteraction = true;
	bAllowDownwardForce = false;

	bDebugIsEnabled = false;

	// Floating Pawn Movement

	MaxSpeed = 500.0;
	Acceleration = 2048.0f;
	Deceleration = 2048.0f;
}
// Initializes the component
void UCustomMovementComponent::InitializeComponent()
{
	Super::InitializeComponent();

	if (UpdatedComponent == NULL){ return; }

	CapsuleComponent = Cast<UCapsuleComponent>(UpdatedComponent);

	CurrentGravityInfo = FGravityInfo();
	CurrentGravityInfo.GravityDirection = -CapsuleComponent->GetUpVector();
	CurrentOrientationInfo = FOrientationInfo();
	CurrentCapsuleRotation = UpdatedComponent->GetComponentRotation();
	CurrentTraceShapeScale = TraceShapeScale;

	TimeInAir = 0.0f;
	bIsInAir = true;
	bCanResetGravity = false;
}
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.

// 2015 , Mhousse1247 ([email protected]) .


#include "CustomGravityPluginPrivatePCH.h"

//Initialization
FGravityInfo UCustomGravityManager::GlobalCustomGravityInfo = FGravityInfo();


UCustomGravityManager::UCustomGravityManager()
{
	//
}

void UCustomGravityManager::SetGlobalCustomGravityPower(float NewGravityPower)
{
	GlobalCustomGravityInfo.GravityPower = NewGravityPower;
}

void UCustomGravityManager::SetGlobalCustomGravityDirection(const FVector& NewGravityDirection)
{
	GlobalCustomGravityInfo.GravityDirection = NewGravityDirection;
}

void UCustomGravityManager::SetGlobalCustomGravityForceMode(EForceMode::Type NewForceMode)
{
	GlobalCustomGravityInfo.ForceMode = NewForceMode;
}
// Called every frame
void UCustomMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// Stop if CapsuleComponet is invalid
	if (CapsuleComponent == NULL)
	{
		return;
	}

	// Update CurrentTraceShapeScale
	if (CurrentTraceShapeScale != TraceShapeScale)
	{
		CurrentTraceShapeScale = FMath::Clamp<float>(TraceShapeScale, 0.0f, 1.0f - KINDA_SMALL_NUMBER);
	}

	/* Local Variables */
	const EDrawDebugTrace::Type DrawDebugType = bDebugIsEnabled ? DebugDrawType : EDrawDebugTrace::None;
	const ECollisionChannel CollisionChannel = CapsuleComponent->GetCollisionObjectType();
	const FVector TraceStart = CapsuleComponent->GetComponentLocation();
	const float CapsuleHalfHeight = CapsuleComponent->GetScaledCapsuleHalfHeight();
	float ShapeRadius = CapsuleComponent->GetScaledCapsuleRadius() * 0.99f;
	FVector TraceEnd = TraceStart - CapsuleComponent->GetUpVector()* (CapsuleHalfHeight - ShapeRadius + GroundHitToleranceDistance + 1.0f);
	FHitResult HitResult;
	TArray<AActor*> ActorsToIgnore;

	
#pragma region Standing/Falling Definition

	/** Testing if the Capsule is in air or standing on a walkable surface*/

	UKismetSystemLibrary::SphereTraceSingle_NEW(this, TraceStart, TraceEnd, ShapeRadius, 
		UEngineTypes::ConvertToTraceType(TraceChannel), true, ActorsToIgnore, DrawDebugType, HitResult, true);
	bIsInAir = !HitResult.bBlockingHit;
	TimeInAir = bIsInAir ? TimeInAir + DeltaTime : 0.0f;
	CurrentStandingSurface = HitResult;

#pragma endregion

#pragma region Update Capsule linearDamping
	/**
	* Update Capsule linearDamping
	* If Grounded L-D is set to 1.0f , in same cases helps to stop the capsule when we are not moving
	* If falling case , stetting L-D to 0.01f , Helps to reach the wanted jump height when JumpImpulse is applied
	*/

	if (CapsuleComponent->GetLinearDamping() != 0.01f && bIsInAir)
	{
		CapsuleComponent->SetLinearDamping(0.01f);
	}
	else if (CapsuleComponent->GetLinearDamping() != 1.0f && !bIsInAir)
	{
		CapsuleComponent->SetLinearDamping(1.0f);
	}
	else if (TimeInAir > 1.0f && PlanetActor != nullptr && !bIsJumping)
	{
		CapsuleComponent->SetLinearDamping(0.5f);
	}
#pragma endregion

#pragma region Gravity Settings

	/*  Gravity Settings : Update Current Gravity info*/

	if (bResetVelocityOnGravitySwitch)
	{
		if (bIsInAir && bCanResetGravity && TimeInAir >= GravitySwitchDelay)
		{
			StopMovementImmediately();
			bCanResetGravity = false;
		}
		else if (!bIsInAir && !bCanResetGravity)
		{
			bCanResetGravity = true;
		}
	}


	if (!bIsInAir && StandingVerticalOrientation == EVerticalOrientation::EVO_SurfaceNormal)
	{

		if (bUseCapsuleHit)
		{
			if (CapsuleHitResult.IsValidBlockingHit())
			{
				CurrentTracedSurface = CapsuleHitResult;
			}
		}

		else
		{
			ShapeRadius = CapsuleComponent->GetScaledCapsuleRadius() * CurrentTraceShapeScale;
			TraceEnd = TraceStart - CapsuleComponent->GetUpVector()* (CapsuleHalfHeight + GroundHitToleranceDistance + 1.0f);

			if (TraceShape == ETraceShape::ETS_Line)
			{
				UKismetSystemLibrary::LineTraceSingle_NEW(this, TraceStart, TraceEnd, 
					UEngineTypes::ConvertToTraceType(TraceChannel), true, ActorsToIgnore, DrawDebugType, HitResult, true);
			}
			else if (TraceShape == ETraceShape::ETS_Sphere)
			{
				TraceEnd += CapsuleComponent->GetUpVector() * ShapeRadius;
				UKismetSystemLibrary::SphereTraceSingle_NEW(this, TraceStart, TraceEnd, ShapeRadius, 
					UEngineTypes::ConvertToTraceType(TraceChannel), true, ActorsToIgnore, DrawDebugType, HitResult, true);
			}
			else
			{
				TraceEnd += CapsuleComponent->GetUpVector() * ShapeRadius;
				UKismetSystemLibrary::BoxTraceSingle(this, TraceStart, TraceEnd, FVector(1, 1, 1)*ShapeRadius, CapsuleComponent->GetComponentRotation(),
					UEngineTypes::ConvertToTraceType(TraceChannel), true, ActorsToIgnore, DrawDebugType, HitResult, true);
			}

			CurrentTracedSurface = HitResult;

		}
		const bool bOnWalkableSurface = CurrentTracedSurface.IsValidBlockingHit();


		if (bOnWalkableSurface)
		{
			SurfaceBasedGravityInfo.GravityDirection = -HitResult.ImpactNormal;
			CurrentGravityInfo = SurfaceBasedGravityInfo;
			CurrentOrientationInfo = OrientationSettings.SurfaceBasedGravity;
		}

	}

	else if ((!bIsInAir && StandingVerticalOrientation == EVerticalOrientation::EVO_GravityDirection) ||
		(bIsInAir && FallingVerticalOrientation == EVerticalOrientation::EVO_GravityDirection))
	{

		if (!bIsInAir || TimeInAir > GravitySwitchDelay)
		{

			switch (CustomGravityType)
			{


			case EGravityType::EGT_Default:
			{
				if (CapsuleComponent->IsGravityEnabled() && GravityScale == 0)
				{
					CapsuleComponent->SetEnableGravity(false);
					CapsuleComponent->SetAllPhysicsLinearVelocity(FVector::ZeroVector);
				}
				else if (!CapsuleComponent->IsGravityEnabled() && GravityScale != 0)
				{
					CapsuleComponent->SetEnableGravity(true);
				}

				CurrentGravityInfo = FGravityInfo(-CapsuleComponent->GetWorld()->GetGravityZ(), -FVector::UpVector, EForceMode::EFM_Acceleration, true);
				CurrentOrientationInfo = OrientationSettings.DefaultGravity;

				UpdateCapsuleRotation(DeltaTime, -CurrentGravityInfo.GravityDirection, CurrentOrientationInfo.bIsInstant, CurrentOrientationInfo.RotationInterpSpeed);

				return;
			}


			case EGravityType::EGT_Custom:
			{
				CurrentGravityInfo = CustomGravityInfo;
				CurrentOrientationInfo = OrientationSettings.CustomGravity;
				break;
			}


			case EGravityType::EGT_GlobalCustom:
			{
				CurrentGravityInfo = UCustomGravityManager::GetGlobalCustomGravityInfo();
				CurrentOrientationInfo = OrientationSettings.GlobalCustomGravity;
				break;
			}


			case EGravityType::EGT_Point:
			{
				if (PlanetActor == NULL) { return; }
				CurrentGravityInfo = PlanetActor->GetGravityinfo(CapsuleComponent->GetComponentLocation());
				CurrentOrientationInfo = OrientationSettings.PointGravity;
				break;
			}
			}
		}
	}

#pragma endregion


	
	/** Variables definition & initialization */
	const FVector CurrentGravityDirection = CurrentGravityInfo.GravityDirection;
	const bool bUseAccelerationChange = (CurrentGravityInfo.ForceMode == EForceMode::EFM_Acceleration);
	const bool bShouldUseStepping = CurrentGravityInfo.bForceSubStepping;
	const float CurrentGravityPower = CurrentGravityInfo.GravityPower * GravityScale;

	const FVector GravityForce = CurrentGravityDirection.GetSafeNormal() * CurrentGravityPower;

	const float InterpSpeed = CurrentOrientationInfo.RotationInterpSpeed;
	const bool bOrientationIsInstant = CurrentOrientationInfo.bIsInstant;

	/* Update Rotation : Orient Capsule's up vector to have the same direction as -gravityDirection */
	UpdateCapsuleRotation(DeltaTime, -CurrentGravityDirection, bOrientationIsInstant, InterpSpeed);

	/* Apply Gravity*/
	ApplyGravity(GravityForce, bShouldUseStepping, bUseAccelerationChange);
}