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


#include "HeadBobComponent.h"

#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"

UHeadBobComponent::UHeadBobComponent()
{
	// C'est le pawn qui appelle UpdateBob depuis son propre Tick, juste avant de
	// composer la transform de la camera. Un tick a nous ne servirait qu'a
	// introduire une incertitude d'ordre.
	PrimaryComponentTick.bCanEverTick = false;

	// Valeurs de depart, a affiner a l'oeil dans BP_FpsPlayer. Elles sont ici et
	// non en initialiseurs de membre pour que le constructeur reste le seul
	// endroit a lire quand on cherche "combien ca bouge par defaut".
	IdleProfile = FHeadBobProfile(0.15f, 0.55f, 0.05f, 0.03f, 0.f);
	WalkProfile = FHeadBobProfile(1.1f, 1.6f, 1.2f, 0.4f, 0.f);
	SprintProfile = FHeadBobProfile(1.55f, 2.1f, 1.7f, 0.8f, 0.f);
	CrouchProfile = FHeadBobProfile(0.7f, 1.f, 0.9f, 0.3f, 0.f);
	AirProfile = FHeadBobProfile(0.f, 0.f, 0.f, 0.f, 0.f);

	// On demarre deja sur le profil d'immobilite : partir de zeros ferait monter
	// la respiration pendant la premiere seconde de jeu, ce qui se voit.
	BlendedProfile = IdleProfile;
}

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

	OwnerCharacter = Cast<ACharacter>(GetOwner());
	if (!OwnerCharacter)
	{
		UE_LOG(LogTemp, Warning, TEXT("UHeadBobComponent : le proprietaire n'est pas un ACharacter, le balancement de tete est desactive."));
		return;
	}

	// GetCharacterMovement() est un simple accesseur sur un membre du Character,
	// mais le stocker evite un appel indirect par frame pour rien.
	OwnerMovement = OwnerCharacter->GetCharacterMovement();
}

void UHeadBobComponent::UpdateBob(float DeltaTime)
{
	// IsLocallyControlled : chez l'hote, le pawn de chaque ami porte AUSSI ce
	// composant. Sans cette garde, le serveur calculerait trois balancements pour
	// des cameras que personne ne regarde.
	if (!bEnableHeadBob || !OwnerCharacter || !OwnerMovement || !OwnerCharacter->IsLocallyControlled())
	{
		// Remise a zero et pas simple sortie : couper l'effet en pleine partie
		// laisserait sinon la camera figee sur le dernier decalage calcule.
		LocationOffset = FVector::ZeroVector;
		RotationOffset = FRotator::ZeroRotator;
		return;
	}

	// Meme garde-fou que les ressorts de camera : un hoquet de framerate ferait
	// sauter la phase de plusieurs pas d'un coup.
	const float Step = FMath::Min(DeltaTime, 1.f / 30.f);

	BlendTowards(SelectTargetProfile(), Step);

	// La phase avance a la frequence FONDUE, pas a celle du profil vise : c'est
	// ce qui fait que le rythme des pas accelere progressivement au demarrage du
	// sprint au lieu de doubler d'une frame a l'autre.
	StridePhase = FMath::Fmod(StridePhase + BlendedProfile.StrideFrequency * 2.f * PI * Step, 2.f * PI);

	const float Intensity = GlobalIntensity * IntensityScale;

	// Une foulee = deux pas. Le balancement lateral suit la foulee entiere (on
	// bascule sur un pied puis sur l'autre), la montee-descente bat deux fois plus
	// vite, une bosse par pied qui touche le sol. C'est ce rapport 1:2 qui dessine
	// le fameux 8 couche et qui fait qu'on lit une marche plutot qu'un flottement.
	const float LateralWave = FMath::Sin(StridePhase);
	const float VerticalWave = FMath::Sin(StridePhase * 2.f);

	// X reste nul : une composante avant-arriere donnerait un effet de zoom qui,
	// en vue premiere personne, se lit comme du mal de mer plus que comme un pas.
	LocationOffset = FVector(
		0.f,
		LateralWave * BlendedProfile.LateralAmount * Intensity,
		VerticalWave * BlendedProfile.VerticalAmount * Intensity);

	// Le roulis suit le balancement lateral : la tete penche du cote ou le corps
	// se porte. Le tangage suit la montee-descente, et vaut 0 par defaut.
	RotationOffset = FRotator(
		VerticalWave * BlendedProfile.PitchAmount * Intensity,
		0.f,
		LateralWave * BlendedProfile.RollAmount * Intensity);
}

const FHeadBobProfile& UHeadBobComponent::SelectTargetProfile() const
{
	if (OwnerMovement->IsFalling())
	{
		return AirProfile;
	}

	// Vitesse a plat : la composante verticale d'une pente ou d'un ascenseur n'a
	// rien a voir avec la cadence des pas.
	const float GroundSpeed = OwnerMovement->Velocity.Size2D();

	// L'immobilite passe AVANT l'accroupissement : accroupi sans bouger, on
	// respire, on ne marche pas sur place.
	if (GroundSpeed < IdleSpeedThreshold)
	{
		return IdleProfile;
	}

	if (OwnerCharacter->bIsCrouched)
	{
		return CrouchProfile;
	}

	return (GroundSpeed > SprintSpeedThreshold) ? SprintProfile : WalkProfile;
}

void UHeadBobComponent::BlendTowards(const FHeadBobProfile& Target, float Step)
{
	BlendedProfile.StrideFrequency = FMath::FInterpTo(BlendedProfile.StrideFrequency, Target.StrideFrequency, Step, ProfileBlendSpeed);
	BlendedProfile.VerticalAmount = FMath::FInterpTo(BlendedProfile.VerticalAmount, Target.VerticalAmount, Step, ProfileBlendSpeed);
	BlendedProfile.LateralAmount = FMath::FInterpTo(BlendedProfile.LateralAmount, Target.LateralAmount, Step, ProfileBlendSpeed);
	BlendedProfile.RollAmount = FMath::FInterpTo(BlendedProfile.RollAmount, Target.RollAmount, Step, ProfileBlendSpeed);
	BlendedProfile.PitchAmount = FMath::FInterpTo(BlendedProfile.PitchAmount, Target.PitchAmount, Step, ProfileBlendSpeed);
}

void UHeadBobComponent::ResetBob()
{
	StridePhase = 0.f;
	BlendedProfile = IdleProfile;
	LocationOffset = FVector::ZeroVector;
	RotationOffset = FRotator::ZeroRotator;
}

void UHeadBobComponent::SetIntensityScale(float NewScale)
{
	IntensityScale = FMath::Max(NewScale, 0.f);
}
