Mech-Shooter / GSCharacterMovementComponent.cpp
GSCharacterMovementComponent.cpp
Raw
// Copyright 2020 Dan Kestranek.
// A lot of boilerplate code supplied by GASShooter under MIT license


#include "Characters/GSCharacterMovementComponent.h"
#include "AbilitySystemComponent.h"
#include "Characters/Abilities/GSAbilitySystemGlobals.h"
#include "Characters/GSCharacterBase.h"
#include "GameplayTagContainer.h"
#include "Characters/ECustomMovementMode.h"
#include "GameFramework/InputSettings.h"

#define SPEARHOOK 0
#define WALLRUN 0

UGSCharacterMovementComponent::UGSCharacterMovementComponent()
{
	SprintSpeedMultiplier = 1.4f;
	ADSSpeedMultiplier = 0.8f;
	KnockedDownSpeedMultiplier = 0.4f;

	KnockedDownTag = FGameplayTag::RequestGameplayTag("State.KnockedDown");
	InteractingTag = FGameplayTag::RequestGameplayTag("State.Interacting");
	InteractingRemovalTag = FGameplayTag::RequestGameplayTag("State.InteractingRemoval");
}

void UGSCharacterMovementComponent::BeginPlay()
{
	Super::BeginPlay();

	if(GetPawnOwner()->GetLocalRole() > ROLE_SimulatedProxy)
	{
		// Binds the client to the OnActorHit delegate function
		GetPawnOwner()->OnActorHit.AddDynamic(this, &UGSCharacterMovementComponent::OnActorHit);
	}
}

float UGSCharacterMovementComponent::GetMaxSpeed() const
{
	AGSCharacterBase* Owner = Cast<AGSCharacterBase>(GetOwner());
	if (!Owner)
	{
		UE_LOG(LogTemp, Error, TEXT("%s() No Owner"), *FString(__FUNCTION__));
		return Super::GetMaxSpeed();
	}

	if (!Owner->IsAlive())
	{
		return 0.0f;
	}

	// Don't move while interacting or being interacted on (revived)
	if (Owner->GetAbilitySystemComponent() && Owner->GetAbilitySystemComponent()->GetTagCount(InteractingTag)
		> Owner->GetAbilitySystemComponent()->GetTagCount(InteractingRemovalTag))
	{
		return 0.0f;
	}

	if (Owner->GetAbilitySystemComponent() && Owner->GetAbilitySystemComponent()->HasMatchingGameplayTag(KnockedDownTag))
	{
		return Owner->GetMoveSpeed() * KnockedDownSpeedMultiplier;
	}

	if (RequestToStartSprinting)
	{
		return Owner->GetMoveSpeed() * SprintSpeedMultiplier;
	}

	if (RequestToStartWallRun)
	{
		return MaxCustomMovementSpeed;
	}
	// Skiing is based off of the character flying movement mode, we need fly speed to enable in editor adjustments.
	if (RequestToStartSkiing)
	{
		return MaxFlySpeed;
	}
	// Boosting is WIP, uses root motion networking which is handled, with most of the editable constraints like graphs, in blueprints
	if (RequestToStartBoost)
	{
		return MaxCustomMovementSpeed;
	}

	if (RequestToStartSpearHook)
	{
		return MaxCustomMovementSpeed;
	}

	if (RequestToStartADS)
	{
		return Owner->GetMoveSpeed() * ADSSpeedMultiplier;
	}
	float spe = Owner->GetMoveSpeed();
	UE_LOG(LogTemp, Warning, TEXT("movespeed: %f"), spe);
	return Owner->GetMoveSpeed();
	
}


/**
 * Overrides UCMCand adds the custom flags for custom networked movement
 * enum CompressedFlags
	{
		FLAG_JumpPressed	= 0x01,	// Jump pressed
		FLAG_WantsToCrouch	= 0x02,	// Wants to crouch
		FLAG_Reserved_1		= 0x04,	// RequestToStartSprinting
		FLAG_Reserved_2		= 0x08,	// RequestToStartADS
		// Remaining bit masks are available for custom flags.
		FLAG_Custom_0		= 0x10, // RequestToStartWallRun
		FLAG_Custom_1		= 0x20, // RequestToStartSkiing
		FLAG_Custom_2		= 0x40, // RequestToStartSpearHook
		FLAG_Custom_3		= 0x80, // UNUSED
	};

	There are reserved flags, but the internet says they have been using them without issues.
	However, it is bad practice to use them as it may break something in a future update, so a clever
	bitwise operator trick can be used -  instead of settling for just 0001, 0010, 0100, and 1000, to try using also
	0011, 0101, 0111, 1001, 1010, 1110, and 1111. More studying required to figure this out.
	https://forums.unrealengine.com/t/how-to-send-more-compressed-flags-through-custom-character-movement-component/421142/6
 */
void UGSCharacterMovementComponent::UpdateFromCompressedFlags(uint8 Flags)
{
	Super::UpdateFromCompressedFlags(Flags);

	//The Flags parameter contains the compressed input flags that are stored in the saved move.
	//UpdateFromCompressed flags simply copies the flags from the saved move into the movement component.
	//It basically just resets the movement component to the state when the move was made so it can simulate from there.
	RequestToStartSprinting = (Flags & FSavedMove_Character::FLAG_Reserved_1) != 0;

	RequestToStartWallRun = (Flags & FSavedMove_Character::FLAG_Custom_0) != 0;

	RequestToStartSkiing = (Flags & FSavedMove_Character::FLAG_Custom_1) != 0;

	RequestToStartSpearHook = (Flags & FSavedMove_Character::FLAG_Custom_2) != 0;

	RequestToStartADS = (Flags & FSavedMove_Character::FLAG_Reserved_2) != 0;
}

FNetworkPredictionData_Client* UGSCharacterMovementComponent::GetPredictionData_Client() const
{
	check(PawnOwner != NULL);

	if (!ClientPredictionData)
	{
		UGSCharacterMovementComponent* MutableThis = const_cast<UGSCharacterMovementComponent*>(this);

		MutableThis->ClientPredictionData = new FGSNetworkPredictionData_Client(*this);
		/// MaxSmoothNetUpdateDist and NoSmoothNetUpdateDist NEED TO BE ADJUSTED if the size of the mech is changed.
		/// These variables cause weird stuttering if not properly tuned.
		MutableThis->ClientPredictionData->MaxSmoothNetUpdateDist = 2000.f;
		MutableThis->ClientPredictionData->NoSmoothNetUpdateDist = 4000.f;
	}

	return ClientPredictionData;
}

/**
* These functions should be called FROM BLUEPRINTS OR C++ when when a key is activated and let go. These wire up
* beautifully with the GAS system, animations, effects, etc. can be triggered off of these client side while the
* server can be told to replicate something else on a custom basis. For example, a sound effect can be activated
* by the autonomous proxy but only on simulated proxies if they are within a certain distance. Another GAS feature
* is to wire it up with stamina or something.
* 
* All these functions do are swap the boolean which triggers it in the Custom Flag. The code that is executed runs
* instantly on the client and is paired up with the server with the time it was executed, and then runs the simulation.
* 
* A START and STOP function are required so that the server knows when the move ends. If sprinting ends on the client
* but the server isn't notified, desync occurs and chaos ensues.
**/

void UGSCharacterMovementComponent::StartSprinting()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Purple, FString::Printf(TEXT("start sprint")));
	RequestToStartSprinting = true;
}

void UGSCharacterMovementComponent::StopSprinting()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Purple, FString::Printf(TEXT("stop sprint")));
	RequestToStartSprinting = false;
}

void UGSCharacterMovementComponent::StartWallRun()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("start wall run")));
	RequestToStartWallRun = true;
}

void UGSCharacterMovementComponent::StopWallRun()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Orange, FString::Printf(TEXT("stop wall run")));
	RequestToStartWallRun = false;
}

void UGSCharacterMovementComponent::StartSkiing()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, FString::Printf(TEXT("start skiing")));
	RequestToStartSkiing = true;
}

void UGSCharacterMovementComponent::StopSkiing()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, FString::Printf(TEXT("stop skiing")));
	RequestToStartSkiing = false;
}

void UGSCharacterMovementComponent::StartBoost()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("start boost")));
	RequestToStartBoost = true;
}

void UGSCharacterMovementComponent::StopBoost()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("stop wall run")));
}

void UGSCharacterMovementComponent::StartSpearHook()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, FString::Printf(TEXT("start spear hook")));
	RequestToStartSpearHook = true;
}

void UGSCharacterMovementComponent::StopSpearHook()
{
	GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Black, FString::Printf(TEXT("stop spear hook")));
	RequestToStartSpearHook = false;
}

void UGSCharacterMovementComponent::StartAimDownSights()
{
	RequestToStartADS = true;
}

void UGSCharacterMovementComponent::StopAimDownSights()
{
	RequestToStartADS = false;
}

/**
* Clear() clears the move data.
**/
void UGSCharacterMovementComponent::FGSSavedMove::Clear()
{
	Super::Clear();

	SavedRequestToStartSprinting = false;
	SavedRequestToStartWallRun = false;
	SavedRequestToStartSkiing = false;
	SavedMoveDirection = FVector::ZeroVector;
	SavedRequestToStartSpearHook = false;
	SavedRequestToStartADS = false;
}

/**
* This function is what uses the Bitwise inclusive operator ( |= ) to switch the flag for the server to run the client initiated code!
**/
uint8 UGSCharacterMovementComponent::FGSSavedMove::GetCompressedFlags() const
{
	uint8 Result = Super::GetCompressedFlags();

	if (SavedRequestToStartSprinting)
	{
		Result |= FLAG_Reserved_1;
	}

	if (SavedRequestToStartWallRun)
	{
		Result |= FLAG_Custom_0;
	}

	if (SavedRequestToStartSkiing)
	{
		Result |= FLAG_Custom_1;
	}

	if (SavedRequestToStartSpearHook)
	{
		Result |= FLAG_Custom_2;
	}

	if (SavedRequestToStartADS)
	{
		Result |= FLAG_Reserved_2;
	}

	return Result;
}

/**
* I am not entirely familar with combining moves, more research required. For now, I follow boilerplate.
**/
bool UGSCharacterMovementComponent::FGSSavedMove::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* Character, float MaxDelta) const
{
	//Set which moves can be combined together. This will depend on the bit flags that are used.
	if (SavedRequestToStartSprinting != ((FGSSavedMove*)NewMove.Get())->SavedRequestToStartSprinting)
	{
		return false;
	}

	if (SavedRequestToStartWallRun != ((FGSSavedMove*)NewMove.Get())->SavedRequestToStartWallRun)
	{
		return false;
	}

	if (SavedRequestToStartSkiing != ((FGSSavedMove*)NewMove.Get())->SavedRequestToStartSkiing)
	{
		return false;
	}
	
	if (SavedMoveDirection != ((FGSSavedMove*)NewMove.Get())->SavedMoveDirection)
	{
		return false;
	}

	if (SavedRequestToStartSpearHook != ((FGSSavedMove*)NewMove.Get())->SavedRequestToStartSpearHook)
	{
		return false;
	}

	if (SavedRequestToStartADS != ((FGSSavedMove*)NewMove.Get())->SavedRequestToStartADS)
	{
		return false;
	}

	return Super::CanCombineWith(NewMove, Character, MaxDelta);
}

/**
* Sets charactermovement data for each move
**/
void UGSCharacterMovementComponent::FGSSavedMove::SetMoveFor(ACharacter* Character, float InDeltaTime, FVector const& NewAccel, FNetworkPredictionData_Client_Character& ClientData)
{
	Super::SetMoveFor(Character, InDeltaTime, NewAccel, ClientData);

	UGSCharacterMovementComponent* CharacterMovement = Cast<UGSCharacterMovementComponent>(Character->GetCharacterMovement());
	if (CharacterMovement)
	{
		SavedRequestToStartSprinting = CharacterMovement->RequestToStartSprinting;
		SavedRequestToStartWallRun = CharacterMovement->RequestToStartWallRun;
		SavedRequestToStartSkiing = CharacterMovement->RequestToStartSkiing;
		SavedMoveDirection = CharacterMovement->MoveDirection;
		SavedRequestToStartSpearHook = CharacterMovement->RequestToStartSpearHook;
		SavedRequestToStartADS = CharacterMovement->RequestToStartADS;
		
	}
}

void UGSCharacterMovementComponent::FGSSavedMove::PrepMoveFor(ACharacter* Character)
{
	Super::PrepMoveFor(Character);

	UGSCharacterMovementComponent* CharacterMovement = Cast<UGSCharacterMovementComponent>(Character->GetCharacterMovement());
	if (CharacterMovement)
	{
		CharacterMovement->MoveDirection = SavedMoveDirection;
	}
}

UGSCharacterMovementComponent::FGSNetworkPredictionData_Client::FGSNetworkPredictionData_Client(const UCharacterMovementComponent& ClientMovement)
	: Super(ClientMovement)
{
}

FSavedMovePtr UGSCharacterMovementComponent::FGSNetworkPredictionData_Client::AllocateNewMove()
{
	return FSavedMovePtr(new FGSSavedMove());
}


void UGSCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
{
	// Phys* functions should only run for characters with ROLE_Authority or ROLE_AutonomousProxy. However, Unreal calls PhysCustom in
	// two seperate locations, one of which doesn't check the role, so we must check it here to prevent this code from running on simulated proxies.
	if (GetOwner()->GetLocalRole() == ROLE_SimulatedProxy)
		return;

	switch (CustomMovementMode)
	{
	case ECustomMovementMode::CMOVE_WallRunning:
		{
			PhysWallRunning(deltaTime, Iterations);
			break;
		}
	case ECustomMovementMode::CMOVE_Skating:
		{
			PhysSkating(deltaTime, Iterations);
			break;
		}
	case ECustomMovementMode::CMOVE_SpearHooking:
		{
			PhysSpearHooking(deltaTime, Iterations);
			break;
		}
		
	}

	

	// Not sure if this is needed
	Super::PhysCustom(deltaTime, Iterations);
}

void UGSCharacterMovementComponent::PhysWallRunning(float deltaTime, int32 Iterations)
{
	// IMPORTANT NOTE: This function (and all other Phys* functions) will be called on characters with ROLE_Authority and ROLE_AutonomousProxy
	// but not ROLE_SimulatedProxy. All movement should be performed in this function so that is runs locally and on the server. UE4 will handle
	// replicating the final position, velocity, etc.. to the other simulated proxies.

	// Make sure the required wall run keys are still down
	if (RequestToStartWallRun == false)
	{
		EndWallRun();
		return;
	}

	// Make sure we're still next to a wall. Provide a vertical tolerance for the line trace since it's possible the the server has
	// moved our character slightly since we've began the wall run. In the event we're right at the top/bottom of a wall we need this
	// tolerance value so we don't immiedetly fall of the wall 
	if (IsNextToWall(LineTraceVerticalTolerance) == false)
	{
		EndWallRun();
		return;
	}

	// Set the owning player's new velocity based on the wall run direction
	FVector newVelocity = WallRunDirection;
	newVelocity.X *= WallRunSpeed;
	newVelocity.Y *= WallRunSpeed;
	newVelocity.Z *= 0.0f;
	Velocity = newVelocity;

	const FVector Adjusted = Velocity * deltaTime;
	FHitResult Hit(1.f);
	SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit);
}

// This function is what allows the custom movement to work. When activated, it runs the code on client then server
void UGSCharacterMovementComponent::PhysSkating(float deltaTime, int32 Iterations)
{
	// Call blueprint-event; default behavior copied from vanilla UCharacterMovementComponent
	if (CharacterOwner)
	{
		CharacterOwner->K2_UpdateCustomMovement(deltaTime);
	}
   
	Velocity += QuakeStrafeAlgorithm(deltaTime);
	
	FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, Velocity.IsZero(), NULL);
	ApplyJumpStateToVelocity();
	ApplyGravityToVelocity(deltaTime);
	ApplyGroundFriction(deltaTime);
	RepositionUsingVelocity(deltaTime, Iterations);
	//ApplyRootMotionToVelocity(deltaTime);
}

// If the player hits something, bounce off
void UGSCharacterMovementComponent::RepositionUsingVelocity(float deltaTime, int32 Iterations)
{
	FHitResult Hit(1.f);
	SafeMoveUpdatedComponent(Velocity * deltaTime, UpdatedComponent->GetComponentQuat(), true, Hit);

	// If hit something (eg wall), cancel out the velocity-component going into the wall and continue moving.
	// The effect is sliding against the wall.
	for (float time_left = deltaTime; Hit.bBlockingHit && Iterations < MaxSimulationIterations; ++Iterations)
	{
		time_left -= time_left * Hit.Time;
		// Make new velocity = old velocity minus the component going directly into the hit
		Velocity -= Velocity.ProjectOnToNormal(-Hit.Normal);
		// Continue moving after sliding off the hit
		SafeMoveUpdatedComponent(Velocity * time_left, UpdatedComponent->GetComponentQuat(), true, Hit);
	}
}

// Allows the mech to "skii"
void UGSCharacterMovementComponent::ApplyGroundFriction(float deltaTime)
{
	// add ground friction unless midair or jumping
	if (CurrentFloor.bBlockingHit && CurrentFloor.FloorDist < 1 && !CharacterOwner->bPressedJump)
	{
		// This can be set to 0 for full on zero friction skiing, however, I feel that including friction helps the feel.
		float groundFrictionAcceleration = 3000.f;
		float adjustedSpeed = Velocity.Size() - groundFrictionAcceleration * deltaTime;
		adjustedSpeed = FMath::Max(adjustedSpeed, 0.f);
		Velocity = Velocity.GetSafeNormal() * adjustedSpeed;
	}
}

// This is what helps achieve the "skiing" feel, fake gravity is applied when the character is in the air.
void UGSCharacterMovementComponent::ApplyGravityToVelocity(float deltaTime)
{
	if (!CurrentFloor.bBlockingHit || CurrentFloor.FloorDist > KINDA_SMALL_NUMBER)
	{
		Velocity.Z += GetGravityZ() * deltaTime;
	}
}

// No jump ability implemented currently for skiing
void UGSCharacterMovementComponent::ApplyJumpStateToVelocity()
{
	if (CharacterOwner->bPressedJump && CurrentFloor.bBlockingHit)
	{
		Velocity.Z = JumpZVelocity;
	}
}

FVector UGSCharacterMovementComponent::QuakeStrafeAlgorithm(float deltaTime)
{
	/**
	 * Helpful sources https://adrianb.io/2015/02/14/bunnyhop.html
	 * https://www.youtube.com/watch?v=v3zT3Z5apaM
	 * From UCharacterMovementComponent.h source code)
	 * Acceleration - Current acceleration vector (with magnitude).
	 * This is calculated each update based on the input vector and the constraints of MaxAcceleration and the current movement mode.
	 * 
	 * GetSafeNormal() - Gets a normalized copy of the vector, checking it is safe to do so based on the length. 
	 * Returns zero vector if vector length is too small to safely normalize.
	 */
	FVector wishDirection = Acceleration.GetSafeNormal(); // The direction that the player wants to move in (the so-called wish direction).
	
	// true if the actor is off the ground AND the dot product of the forward vector and wish direction is less than 0.2f, meaning the player is holding forward.
	bool useCustomQuakeAirAccel = (!CurrentFloor.bBlockingHit || CharacterOwner->bPressedJump)
					   && FMath::Abs((UpdatedComponent->GetForwardVector() | wishDirection)) < 0.2f;

	// The current velocity before any calculations. The dot product of velocity onto wish direction.
	float currentProjectionVelocity = Velocity | wishDirection; 
	// RunSpeed is adjusted if not holding forward
	float runSpeed = useCustomQuakeAirAccel ? RunSpeed / 10.f : RunSpeed;
	//GEngine->AddOnScreenDebugMessage(-10, 1.f, FColor::Yellow, FString::Printf(TEXT("run speed: %f"), runSpeed));
	// Add speed
	float addSpeed = runSpeed - currentProjectionVelocity;
	// Air accel increases air acceleration if holding forward
	float airAccel = useCustomQuakeAirAccel ? MaxAccelAir * 70 : MaxAccelAir;
	// Max accel changes based on being in air, or on the ground.
	float maxAccel = (!CurrentFloor.bBlockingHit || CharacterOwner->bPressedJump) ? airAccel : MaxAccelGround;
	// This is the "bug", addSpeed is clipped between the difference between RunSpeed and current speed (currentProjectionVelocity) https://youtu.be/v3zT3Z5apaM?t=152
	addSpeed = FMath::Max<float>(FMath::Min(addSpeed, maxAccel * deltaTime), 0);
	return wishDirection * addSpeed;
}



bool UGSCharacterMovementComponent::IsCustomMovementMode(uint8 custom_movement_mode) const
{
	return MovementMode == EMovementMode::MOVE_Custom && CustomMovementMode == custom_movement_mode;
}

void UGSCharacterMovementComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{
	AGSCharacterBase* Owner = Cast<AGSCharacterBase>(GetOwner());
	if (Owner != nullptr && Owner->GetLocalRole() > ROLE_SimulatedProxy)
	{
		// Unbind from all events
		Owner->OnActorHit.RemoveDynamic(this, &UGSCharacterMovementComponent::OnActorHit);
	}

	Super::OnComponentDestroyed(bDestroyingHierarchy);
}

#ifdef WALLRUN
/// <summary>
/// Wall run is currently poorly implemented
/// </summary>
/// <returns></returns>
bool UGSCharacterMovementComponent::BeginWallRun()
{
	// Only allow wall running to begin if the required keys are down
	if (RequestToStartWallRun)
	{
		GEngine->AddOnScreenDebugMessage(-1, 12.f, FColor::Orange, TEXT("BeginWallRun"));
		// Set the movement mode to wall running. UE4 will handle replicating this change to all connected clients.
		SetMovementMode(EMovementMode::MOVE_Custom, ECustomMovementMode::CMOVE_WallRunning);
		return true;
	}

	return false;
}

void UGSCharacterMovementComponent::OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode)
{
	if (MovementMode == MOVE_Custom)
	{
		switch (CustomMovementMode)
		{
			// Did we just start wall running?
		case ECustomMovementMode::CMOVE_WallRunning:
		{
			// Stop current movement and constrain the character to only horizontal movement
			StopMovementImmediately();
			bConstrainToPlane = true;
			SetPlaneConstraintNormal(FVector(0.0f, 0.0f, 1.0f));
		}
		break;
		}
	}

	if (PreviousMovementMode == MOVE_Custom)
	{
		switch (PreviousCustomMode)
		{
			// Did we just finish wall running?
		case ECustomMovementMode::CMOVE_WallRunning:
		{
			// Unconstrain the character from horizontal movement
			bConstrainToPlane = false;
		}
		break;
		}
	}

	Super::OnMovementModeChanged(PreviousMovementMode, PreviousCustomMode);
}
bool UGSCharacterMovementComponent::IsNextToWall(float vertical_tolerance)
{
	// Do a line trace from the player into the wall to make sure we're stil along the side of a wall
	FVector crossVector = WallRunSide == EWallRunSide::kLeft ? FVector(0.0f, 0.0f, -1.0f) : FVector(0.0f, 0.0f, 1.0f);
	FVector traceStart = GetPawnOwner()->GetActorLocation() + (WallRunDirection * 20.0f);
	FVector traceEnd = traceStart + (FVector::CrossProduct(WallRunDirection, crossVector) * 100);
	FHitResult hitResult;

	// Create a helper lambda for performing the line trace
	auto lineTrace = [&](const FVector& start, const FVector& end)
	{
		return (GetWorld()->LineTraceSingleByChannel(hitResult, start, end, ECollisionChannel::ECC_Visibility));
	};

	// If a vertical tolerance was provided we want to do two line traces - one above and one below the calculated line
	if (vertical_tolerance > FLT_EPSILON)
	{
		// If both line traces miss the wall then return false, we're not next to a wall
		if (lineTrace(FVector(traceStart.X, traceStart.Y, traceStart.Z + vertical_tolerance / 2.0f), FVector(traceEnd.X, traceEnd.Y, traceEnd.Z + vertical_tolerance / 2.0f)) == false &&
			lineTrace(FVector(traceStart.X, traceStart.Y, traceStart.Z - vertical_tolerance / 2.0f), FVector(traceEnd.X, traceEnd.Y, traceEnd.Z - vertical_tolerance / 2.0f)) == false)
		{
			return false;
		}
	}
	// If no vertical tolerance was provided we just want to do one line trace using the caclulated line
	else
	{
		// return false if the line trace misses the wall
		if (lineTrace(traceStart, traceEnd) == false)
			return false;
	}

	// Make sure we're still on the side of the wall we expect to be on
	EWallRunSide newWallRunSide;
	FindWallRunDirectionAndSide(hitResult.ImpactNormal, WallRunDirection, newWallRunSide);
	if (newWallRunSide != WallRunSide)
	{
		return false;
	}

	return true;
}

void UGSCharacterMovementComponent::EndWallRun()
{
	// Set the movement mode back to falling
	SetMovementMode(EMovementMode::MOVE_Falling);
}

void UGSCharacterMovementComponent::FindWallRunDirectionAndSide(const FVector& surface_normal, FVector& direction, EWallRunSide& side) const
{
	FVector crossVector;

	if (FVector2D::DotProduct(FVector2D(surface_normal), FVector2D(GetPawnOwner()->GetActorRightVector())) > 0.0)
	{
		side = EWallRunSide::kRight;
		crossVector = FVector(0.0f, 0.0f, 1.0f);
	}
	else
	{
		side = EWallRunSide::kLeft;
		crossVector = FVector(0.0f, 0.0f, -1.0f);
	}

	// Find the direction parallel to the wall in the direction the player is moving
	direction = FVector::CrossProduct(surface_normal, crossVector);
}

bool UGSCharacterMovementComponent::CanSurfaceBeWallRan(const FVector& surface_normal) const
{
	// Return false if the surface normal is facing down
	if (surface_normal.Z < -0.05f)
		return false;

	FVector normalNoZ = FVector(surface_normal.X, surface_normal.Y, 0.0f);
	normalNoZ.Normalize();

	// Find the angle of the wall
	float wallAngle = FMath::Acos(FVector::DotProduct(normalNoZ, surface_normal));

	// Return true if the wall angle is less than the walkable floor angle
	return wallAngle < GetWalkableFloorAngle();
}

void UGSCharacterMovementComponent::OnActorHit(AActor* SelfActor, AActor* OtherActor, FVector NormalImpulse, const FHitResult& Hit)
{
	if (IsCustomMovementMode(ECustomMovementMode::CMOVE_WallRunning))
		return;

	// Make sure we're falling. Wall running can only begin if we're currently in the air
	if (!(!CurrentFloor.bBlockingHit || CurrentFloor.FloorDist > KINDA_SMALL_NUMBER))
		return;

	// Make sure the surface can be wall ran based on the angle of the surface that we hit
	if (CanSurfaceBeWallRan(Hit.ImpactNormal) == false)
		return;

	// Update the wall run direction and side
	FindWallRunDirectionAndSide(Hit.ImpactNormal, WallRunDirection, WallRunSide);

	// Make sure we're next to a wall
	if (IsNextToWall() == false)
		return;
	
	BeginWallRun();
}
#endif
/**
* This is called from OnMovementUpdated, which acts like an update, it is called on tick.
* It is only triggered if the "ski" key is triggered. The ski key is called from blueprints and linked up with GAS functionality
**/
bool UGSCharacterMovementComponent::BeginSki()
{
		// Set the movement mode to skiing. UE4 will handle replicating this change to all connected clients.
		SetMovementMode(EMovementMode::MOVE_Custom, ECustomMovementMode::CMOVE_Skating);
		return true;
}

/**
* END SKI IS DISABLED FOR TESTING PURPOSES
**/
void UGSCharacterMovementComponent::EndSki()
{
	/*
	// Set the movement mode back to falling
	if (!IsCustomMovementMode(ECustomMovementMode::CMOVE_Skating))
		return;
	SetMovementMode(DefaultLandMovementMode);
	*/
}
#ifdef SPEARHOOK
// TODO: Finish Spear Hook implementation
void UGSCharacterMovementComponent::BeginSpearHook(FHitResult HitResult)
{
	GetLineTraceForSpearHook();
	GEngine->AddOnScreenDebugMessage(-1, 1, FColor::Orange, FString::Printf(TEXT("Trace Hit: %s"), *HitResult.Location.ToString()));
	SetMovementMode(EMovementMode::MOVE_Custom, ECustomMovementMode::CMOVE_SpearHooking);
	SpearHookHit = HitResult;
}

// TODO: Finish Spear Hook implementation
void UGSCharacterMovementComponent::PhysSpearHooking(float deltaTime, int32 Iterations)
{
	if (CharacterOwner)
	{
		CharacterOwner->K2_UpdateCustomMovement(deltaTime);
	}
   
	Velocity += GetSpearHookingHorizontalAddVelocity(deltaTime);
	
	FindFloor(UpdatedComponent->GetComponentLocation(), CurrentFloor, Velocity.IsZero(), NULL);
	ApplyJumpStateToVelocity();
	ApplyGravityToVelocity(deltaTime);
	ApplyGroundFriction(deltaTime);
	RepositionUsingVelocity(deltaTime, Iterations);
}

// TODO: Finish Spear Hook implementation
FVector UGSCharacterMovementComponent::GetSpearHookingHorizontalAddVelocity(float deltaTime)
{
	/**
	 * Helpful sources https://adrianb.io/2015/02/14/bunnyhop.html
	 * https://www.youtube.com/watch?v=v3zT3Z5apaM
	 * From UCharacterMovementComponent.h source code)
	 * Acceleration - Current acceleration vector (with magnitude).
	 * This is calculated each update based on the input vector and the constraints of MaxAcceleration and the current movement mode.
	 *
	 * GetSafeNormal() - Gets a normalized copy of the vector, checking it is safe to do so based on the length.
	 * Returns zero vector if vector length is too small to safely normalize.
	 */
	FVector wishDirection = Acceleration.GetSafeNormal(); // The direction that the player wants to move in (the so-called wish direction).

	// true if the actor is off the ground AND the dot product of the forward vector and wish direction is less than 0.2f, meaning the player is holding forward.
	bool useCustomQuakeAirAccel = (!CurrentFloor.bBlockingHit || CharacterOwner->bPressedJump)
		&& FMath::Abs((UpdatedComponent->GetForwardVector() | wishDirection)) < 0.2f;

	// The current velocity before any calculations. The dot product of velocity onto wish direction.
	float currentProjectionVelocity = Velocity | wishDirection;
	// RunSpeed is adjusted if not holding forward
	float runSpeed = useCustomQuakeAirAccel ? RunSpeed / 10.f : RunSpeed;
	//GEngine->AddOnScreenDebugMessage(-10, 1.f, FColor::Yellow, FString::Printf(TEXT("run speed: %f"), runSpeed));
	// Add speed
	float addSpeed = runSpeed - currentProjectionVelocity;
	// Air accel increases air acceleration if holding forward
	float airAccel = useCustomQuakeAirAccel ? MaxAccelAir * 70 : MaxAccelAir;
	// Max accel changes based on being in air, or on the ground.
	float maxAccel = (!CurrentFloor.bBlockingHit || CharacterOwner->bPressedJump) ? airAccel : MaxAccelGround;
	// This is the "bug", addSpeed is clipped between the difference between RunSpeed and current speed (currentProjectionVelocity) https://youtu.be/v3zT3Z5apaM?t=152
	addSpeed = FMath::Max<float>(FMath::Min(addSpeed, maxAccel * deltaTime), 0);
	return wishDirection * addSpeed;
}


void UGSCharacterMovementComponent::GetLineTraceForSpearHook()
{
/*
	FVector TraceStart = GetPawnOwner()->GetActorLocation();
	FVector TraceEnd = (TraceStart * 1000.f);

	FHitResult Hit;
	FCollisionQueryParams TraceParams;
	bool bHit= GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, TraceParams);
	DrawDebugLine(GetWorld(), TraceStart, TraceEnd, FColor::Orange, false, 5.f);

	if (bHit)
	{
		
	}
	*/
}
#endif

// RPC code for boost, may use this again or stick with root motion implementation
bool UGSCharacterMovementComponent::ServerSetMoveDirection_Validate(const FVector MoveDir)
{
	return true;
}

// RPC code for boost, may use this again or stick with root motion implementation
void UGSCharacterMovementComponent::ServerSetMoveDirection_Implementation(const FVector MoveDir)
{
	MoveDirection = MoveDir;
}

//Called on tick, can be used for setting values and movement mode
void UGSCharacterMovementComponent::OnMovementUpdated(float DeltaSeconds, const FVector& OldLocation, const FVector& OldVelocity)
{
	Super::OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity); 
	AGSCharacterBase* Owner = Cast<AGSCharacterBase>(GetOwner());
	if (!CharacterOwner)
	{
		return;
	}

	// Store movement vector
	if (Owner->IsLocallyControlled())
	{
		MoveDirection = Owner->GetLastMovementInputVector();
	}

	// Send movement vector to server
	if (Owner->GetLocalRole() < ROLE_Authority)
	{
		ServerSetMoveDirection(MoveDirection);
	}

	
	if (RequestToStartBoost)
	{
		/*
		MoveDirection.Normalize();
		FVector BoostVel = MoveDirection*5000;
		BoostVel.Z = 0.0f;

		Launch(BoostVel);
		RequestToStartBoost = false;
		*/
	}
	//GroundFriction = CustomGroundFriction;
	//GravityScale = MaxGravity;
	//PlaneConstraintNormal = PlaneConstraint;
	//JumpZVelocity = MaxJumpHeightCustom;
	//AirControl = AirControlCustom;
	//CrouchedHalfHeight = CustomCrouchHalfHeight;


	//AMultiCharacter* MultiCharacter = static_cast<AMultiCharacter*>(PawnOwner);
	
	if (RequestToStartSkiing)
	{
		//GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0.1f, FColor::Purple, FString::Printf(TEXT("velocity: %f"), Owner->GetVelocity().Length()));
		if (IsCustomMovementMode(ECustomMovementMode::CMOVE_Skating))
		{
			return;
		}
		BeginSki();
		
	}
	else if (!RequestToStartSkiing)
	{
		if (IsCustomMovementMode(ECustomMovementMode::CMOVE_Skating))
		{
			EndSki();
		}
	}
	/*
	if (Owner)
	{
		//Owner->OnMovementUpdatedCustom(DeltaSeconds, OldLocation, OldVelocity); 
	}

	if (IsCompressedFlagSet(EMovementFlag::CFLAG_NewMovementMode))
	{
		SetMovementMode(EMovementMode(CustomNewMovementMode), CustomNewCustomMovementMode);
		JustChangedMovementMode = 1; 
       
	}

	if (IsCompressedFlagSet(EMovementFlag::CFLAG_NewMovementMode) == false && JustChangedMovementMode == 1)
	{
		SetMovementMode(DefaultLandMovementMode, 0);
		JustChangedMovementMode = 0; 
         
	}

	if ((MovementMode != MOVE_None) && IsActive() && HasValidData())
	{
		PendingLaunchVelocity = LaunchVelocityCustom;
		LaunchVelocityCustom = FVector(0.f, 0.f, 0.f); 
	}
	*/
}