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


#include "FpsPlayer.h"

#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h"
#include "InputActionValue.h"
#include "InteractionComponent.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "PickupItem.h"
#include "SurvivalStatsComponent.h"
#include "SurvivalUserSettings.h"

// Sets default values
AFpsPlayer::AFpsPlayer()
{
 	// Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	// Gabarit du joueur : ~176 cm de haut, 34 cm de rayon
	UCapsuleComponent* Capsule = GetCapsuleComponent();
	Capsule->InitCapsuleSize(34.f, 88.f);

	// La souris fait tourner le personnage horizontalement (yaw) ;
	// le pitch reste sur le controller et n'est applique qu'a la camera.
	bUseControllerRotationYaw = true;
	bUseControllerRotationPitch = false;
	bUseControllerRotationRoll = false;

	// Camera a hauteur des yeux. La position est exprimee par rapport au centre
	// de la capsule, d'ou la soustraction de la demi-hauteur.
	FirstPersonCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
	FirstPersonCamera->SetupAttachment(Capsule);
	FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, EyeHeightStanding - Capsule->GetUnscaledCapsuleHalfHeight()));
	FirstPersonCamera->SetFieldOfView(BaseFOV);
	// On pilote la rotation nous-memes dans UpdateCameraEffects() : bUsePawnControlRotation
	// ecraserait la rotation chaque frame et tous les effets de tilt seraient invisibles.
	FirstPersonCamera->bUsePawnControlRotation = false;

	InteractionComponent = CreateDefaultSubobject<UInteractionComponent>(TEXT("InteractionComponent"));
	InventoryComponent = CreateDefaultSubobject<UInventoryComponent>(TEXT("InventoryComponent"));
	SurvivalStats = CreateDefaultSubobject<USurvivalStatsComponent>(TEXT("SurvivalStats"));

	// En vue FPS on ne veut pas voir son propre corps (mesh 3e personne),
	// mais on garde son ombre.
	GetMesh()->SetOwnerNoSee(true);
	GetMesh()->SetCastHiddenShadow(true);

	UCharacterMovementComponent* Movement = GetCharacterMovement();
	Movement->MaxWalkSpeed = WalkSpeed;
	Movement->MaxWalkSpeedCrouched = CrouchSpeed;
	Movement->JumpZVelocity = 450.f;
	Movement->AirControl = 0.3f;
	Movement->BrakingDecelerationWalking = 2000.f;
	// 120 cm de haut accroupi (une vraie position accroupie, pas un rampant)
	Movement->SetCrouchedHalfHeight(60.f);
	// Indispensable, sinon Crouch() est ignore
	Movement->GetNavAgentPropertiesRef().bCanCrouch = true;
}

// Called when the game starts or when spawned
void AFpsPlayer::BeginPlay()
{
	Super::BeginPlay();

	SurvivalStats->OnDied.AddDynamic(this, &AFpsPlayer::HandleDeath);

	// Les reglages d'abord : le pawn est recree a chaque respawn et repart des
	// valeurs de son CDO, il doit donc les relire lui-meme. L'abonnement couvre
	// ensuite les changements faits en cours de partie depuis le menu de pause.
	ApplyUserSettings();

	if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
	{
		Settings->OnSurvivalSettingsApplied.AddDynamic(this, &AFpsPlayer::ApplyUserSettings);
	}

	// Le FOV est interpole chaque frame vers BaseFOV : sans ce calage initial on
	// verrait la camera s'ouvrir lentement jusqu'a la valeur reglee a chaque
	// apparition.
	FirstPersonCamera->SetFieldOfView(BaseFOV);

	// On enregistre le mapping context aupres du sous-systeme Enhanced Input
	// du joueur local. Sans ca, aucune touche n'est lue.
	if (const APlayerController* PlayerController = Cast<APlayerController>(GetController()))
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
			ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
		{
			if (DefaultMappingContext)
			{
				// On ACTIVE le contexte ici, mais on ne le DECLARE pas comme
				// remappable : c'est USurvivalGameInstance qui s'en charge, parce
				// que le menu principal n'a pas de pawn et doit quand meme
				// afficher les touches.
				Subsystem->AddMappingContext(DefaultMappingContext, 0);
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DefaultMappingContext n'est pas assigne, aucun input ne fonctionnera."));
			}
		}
	}
}

void AFpsPlayer::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	// Le singleton de reglages survit au monde : sans ce desabonnement, chaque
	// cadavre laisserait derriere lui un abonne mort de plus.
	if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
	{
		Settings->OnSurvivalSettingsApplied.RemoveDynamic(this, &AFpsPlayer::ApplyUserSettings);
	}

	Super::EndPlay(EndPlayReason);
}

void AFpsPlayer::ApplyUserSettings()
{
	USurvivalUserSettings* Settings = USurvivalUserSettings::Get();
	if (!Settings)
	{
		// Pas de reglages : on garde les valeurs du Blueprint. Le jeu reste
		// jouable, il n'est simplement pas configurable.
		UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : USurvivalUserSettings introuvable, les valeurs du Blueprint sont conservees."));
		return;
	}

	LookSensitivityX = Settings->GetLookMultiplierX();
	LookSensitivityY = Settings->GetLookMultiplierY();
	bInvertLookY = Settings->GetInvertLookY();
	BaseFOV = Settings->GetFieldOfView();
	CameraShakeMultiplier = Settings->GetCameraShakeMultiplier();
	bSprintToggleMode = Settings->GetSprintToggle();
	bCrouchToggleMode = Settings->GetCrouchToggle();

	// Repasser en maintien alors que la touche n'est plus enfoncee laisserait le
	// personnage bloque en sprint : on repart d'un etat propre.
	if (!bSprintToggleMode && bIsSprinting)
	{
		StopSprint();
	}
}

// Called every frame
void AFpsPlayer::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	// On memorise la vitesse de chute a chaque frame : au moment ou Landed()
	// est appele, la vitesse a deja pu etre remise a zero par le moteur.
	if (GetCharacterMovement()->IsFalling())
	{
		LastFallSpeed = GetCharacterMovement()->Velocity.Z;
	}

	UpdateCameraEffects(DeltaTime);
}

void AFpsPlayer::UpdateCameraEffects(float DeltaTime)
{
	// Un gros hoquet de framerate (chargement, breakpoint) ferait diverger
	// l'integration du ressort ci-dessous. On plafonne le pas de temps.
	const float Step = FMath::Min(DeltaTime, 1.f / 30.f);

	// ==================================================================
	// 1. Animation d'accroupissement
	// ==================================================================
	// CrouchAlpha va de 0 (debout) a 1 (accroupi) en CrouchTransitionDuration
	// secondes. Duree fixe plutot qu'un FInterpTo : le mouvement est identique
	// a chaque fois et se termine vraiment, au lieu de tendre asymptotiquement.
	if (!FMath::IsNearlyEqual(CrouchAlpha, CrouchTargetAlpha))
	{
		const float CrouchStep = Step / FMath::Max(CrouchTransitionDuration, KINDA_SMALL_NUMBER);
		CrouchAlpha = FMath::Clamp(CrouchAlpha + CrouchStep * FMath::Sign(CrouchTargetAlpha - CrouchAlpha), 0.f, 1.f);
	}

	// ==================================================================
	// 2. Ressort d'impact (saut, atterrissage, et tout AddCameraPunch)
	// ==================================================================
	// L'impulsion n'est pas versee d'un coup : la vitesse de la camera passerait
	// de 0 a plusieurs centaines de cm/s en une frame, et cette discontinuite se
	// lit comme un a-coup. On l'etale sur PunchImpulseRampTime.
	if (!FMath::IsNearlyZero(PendingPunch))
	{
		const float ReleaseRatio = (PunchImpulseRampTime > KINDA_SMALL_NUMBER)
			? FMath::Clamp(Step / PunchImpulseRampTime, 0.f, 1.f)
			: 1.f;
		const float Released = PendingPunch * ReleaseRatio;
		PunchVelocity += Released;
		PendingPunch -= Released;
	}

	// Ressort amorti classique : acceleration = -k*x - c*v.
	// On exprime k et c a partir d'une frequence et d'un taux d'amortissement,
	// beaucoup plus parlants a regler qu'une raideur brute.
	const float Omega = 2.f * PI * PunchFrequency;
	const float Acceleration = -(Omega * Omega * PunchOffset) - (2.f * PunchDamping * Omega * PunchVelocity);
	PunchVelocity += Acceleration * Step;
	PunchOffset = FMath::Clamp(PunchOffset + PunchVelocity * Step, -PunchMaxOffset, PunchMaxOffset);

	// ==================================================================
	// 3. FOV de sprint
	// ==================================================================
	// On exige un vrai deplacement : sinon le FOV s'ouvrirait juste en
	// maintenant Shift a l'arret, ce qui donne un effet de pompage bizarre.
	const bool bMovingFastEnough = GetCharacterMovement()->Velocity.SizeSquared2D() > FMath::Square(WalkSpeed * 0.9f);
	const float TargetFOV = BaseFOV + ((bIsSprinting && bMovingFastEnough) ? SprintFOVOffset : 0.f);
	FirstPersonCamera->SetFieldOfView(FMath::FInterpTo(FirstPersonCamera->FieldOfView, TargetFOV, Step, FOVInterpSpeed));

	// ==================================================================
	// 4. Application : position
	// ==================================================================
	// SmoothStep donne une courbe en S : demarrage doux, acceleration, arrivee douce.
	const float Smoothed = FMath::SmoothStep(0.f, 1.f, CrouchAlpha);
	const float EyeHeightAboveFeet = FMath::Lerp(EyeHeightStanding, EyeHeightCrouched, Smoothed);

	// Point cle : quand la capsule retrecit, le CharacterMovement redescend l'acteur
	// d'un coup pour garder les pieds au sol. La camera, attachee a la capsule,
	// suivait ce saut instantane -- c'est ce qui rendait le crouch "snap".
	// En raisonnant en hauteur d'oeil AU-DESSUS DES PIEDS et en soustrayant la
	// demi-hauteur courante de la capsule, ce saut est annule exactement.
	const float CapsuleHalfHeight = GetCapsuleComponent()->GetScaledCapsuleHalfHeight();
	FirstPersonCamera->SetRelativeLocation(FVector(0.f, 0.f, EyeHeightAboveFeet - CapsuleHalfHeight + PunchOffset));

	// ==================================================================
	// 5. Application : rotation
	// ==================================================================
	// sin(alpha * PI) vaut 0 aux deux extremites et 1 au milieu : le tilt monte
	// puis redescend tout seul pendant la transition, et est nul au repos.
	// CrouchTiltDirection inverse le sens selon qu'on descend ou qu'on remonte.
	const float CrouchTiltShape = FMath::Sin(CrouchAlpha * PI) * CrouchTiltDirection;

	FRotator ViewOffset(-CrouchTiltPitch * CrouchTiltShape, 0.f, CrouchTiltRoll * CrouchTiltShape);
	// Le regard suit le deplacement vertical de la camera : elle descend, on
	// regarde vers le bas. C'est ce qui vend l'impact a l'atterrissage.
	ViewOffset.Pitch += PunchOffset * PunchPitchPerCm;

	if (const AController* OwningController = GetController())
	{
		FirstPersonCamera->SetWorldRotation(OwningController->GetControlRotation() + ViewOffset);
	}
}

void AFpsPlayer::AddCameraPunch(float Strength)
{
	// Le reglage s'applique ICI et pas sur chaque source d'impact : tout ce qui
	// secoue la camera passe par cette porte, y compris les explosions et les
	// reculs d'arme a venir. A 0, la camera ne bouge plus du tout.
	// Mis en attente plutot qu'applique directement : c'est UpdateCameraEffects
	// qui l'injecte progressivement dans le ressort.
	PendingPunch += Strength * CameraShakeMultiplier;
}

void AFpsPlayer::Landed(const FHitResult& Hit)
{
	Super::Landed(Hit);

	// L'amplitude suit la vitesse de chute : une marche d'escalier ne doit pas
	// secouer autant qu'une chute de dix metres.
	const float FallSpeed = FMath::Abs(LastFallSpeed);
	if (FallSpeed > LandPunchMinFallSpeed)
	{
		const float Range = FMath::Max(LandPunchMaxFallSpeed - LandPunchMinFallSpeed, 1.f);
		const float Ratio = FMath::Clamp((FallSpeed - LandPunchMinFallSpeed) / Range, 0.f, 1.f);
		AddCameraPunch(-LandPunchStrength * Ratio);

		// Aide au reglage : montre la vitesse d'impact reelle et l'amplitude
		// obtenue. Supprime ce log quand le ressenti te convient.
		UE_LOG(LogTemp, Log, TEXT("Atterrissage : chute %.0f cm/s, ratio %.2f, impulsion %.0f cm/s (~%.1f cm de camera)"),
			FallSpeed, Ratio, LandPunchStrength * Ratio, LandPunchStrength * Ratio * 0.018f);
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT("Atterrissage ignore : chute %.0f cm/s, sous le seuil de %.0f cm/s"),
			FallSpeed, LandPunchMinFallSpeed);
	}

	LastFallSpeed = 0.f;
}

void AFpsPlayer::OnJumped_Implementation()
{
	Super::OnJumped_Implementation();

	AddCameraPunch(-JumpPunchStrength);
}

// Called to bind functionality to input
void AFpsPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
	if (!Input)
	{
		UE_LOG(LogTemp, Error, TEXT("AFpsPlayer : l'input component n'est pas un UEnhancedInputComponent."));
		return;
	}

	if (MoveAction)
	{
		Input->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AFpsPlayer::Move);
	}

	if (LookAction)
	{
		Input->BindAction(LookAction, ETriggerEvent::Triggered, this, &AFpsPlayer::Look);
	}

	if (JumpAction)
	{
		// Jump() et StopJumping() viennent directement de ACharacter
		Input->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
		Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
	}

	if (SprintAction)
	{
		// Le mode maintien / bascule se decide dans les handlers et non ici :
		// rebrancher les bindings a chaud demanderait de reconstruire l'input
		// component a chaque changement de reglage.
		Input->BindAction(SprintAction, ETriggerEvent::Started, this, &AFpsPlayer::StartSprint);
		Input->BindAction(SprintAction, ETriggerEvent::Completed, this, &AFpsPlayer::HandleSprintReleased);
	}

	if (CrouchAction)
	{
		Input->BindAction(CrouchAction, ETriggerEvent::Started, this, &AFpsPlayer::StartCrouch);
		Input->BindAction(CrouchAction, ETriggerEvent::Completed, this, &AFpsPlayer::HandleCrouchReleased);
	}

	if (InteractAction)
	{
		Input->BindAction(InteractAction, ETriggerEvent::Started, this, &AFpsPlayer::Interact);
	}

	if (UseItemAction)
	{
		Input->BindAction(UseItemAction, ETriggerEvent::Started, this, &AFpsPlayer::UseSelectedItem);
	}
}

void AFpsPlayer::Move(const FInputActionValue& Value)
{
	const FVector2D Axis = Value.Get<FVector2D>();
	if (Axis.IsNearlyZero() || !Controller)
	{
		return;
	}

	// On se deplace par rapport a la direction du regard, mais a plat :
	// on ne garde que le yaw pour ne pas "voler" en regardant vers le haut.
	const FRotator YawRotation(0.f, Controller->GetControlRotation().Yaw, 0.f);
	const FVector Forward = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
	const FVector Right = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	AddMovementInput(Forward, Axis.Y);
	AddMovementInput(Right, Axis.X);
}

void AFpsPlayer::Look(const FInputActionValue& Value)
{
	// Si IA_Look n'est pas en Axis2D, Get<FVector2D>() renvoie toujours Y = 0
	// et le regard reste bloque sur l'axe horizontal. On le signale une fois.
	if (Value.GetValueType() != EInputActionValueType::Axis2D && !bLoggedLookTypeWarning)
	{
		bLoggedLookTypeWarning = true;
		UE_LOG(LogTemp, Error,
			TEXT("AFpsPlayer : IA_Look doit avoir Value Type = Axis2D (Vector2D). ")
			TEXT("Avec un autre type, l'axe vertical est ignore."));
	}

	const FVector2D Axis = Value.Get<FVector2D>();

	// Pas de DeltaTime ici : la souris fournit un delta, pas une vitesse.
	// Le multiplier par DeltaTime rendrait la visee dependante du framerate.
	AddControllerYawInput(Axis.X * LookSensitivityX);
	AddControllerPitchInput(Axis.Y * LookSensitivityY * (bInvertLookY ? -1.f : 1.f));
}

void AFpsPlayer::StartSprint()
{
	// Pas de sprint accroupi : on se releve d'abord
	if (bIsCrouched)
	{
		return;
	}

	// En mode bascule, la meme touche coupe le sprint.
	if (bSprintToggleMode && bIsSprinting)
	{
		StopSprint();
		return;
	}

	bIsSprinting = true;
	GetCharacterMovement()->MaxWalkSpeed = SprintSpeed;
}

void AFpsPlayer::StopSprint()
{
	bIsSprinting = false;
	GetCharacterMovement()->MaxWalkSpeed = WalkSpeed;
}

void AFpsPlayer::HandleSprintReleased()
{
	// En bascule, relacher la touche ne doit rien faire.
	if (bSprintToggleMode)
	{
		return;
	}

	StopSprint();
}

void AFpsPlayer::UseSelectedItem()
{
	const FInventorySlot Selected = InventoryComponent->GetSelectedSlot();
	if (Selected.IsEmpty())
	{
		return;
	}

	const UItemDataAsset* Item = Selected.Item;

	// Objet non consommable : on ne fait rien pour l'instant. C'est ici que
	// viendront les outils (frapper), les armes (tirer), les placeables.
	if (!Item->bIsConsumable)
	{
		return;
	}

	// Les valeurs de restauration sont appliquees a CHAQUE utilisation.
	SurvivalStats->ApplyRestore(Item->HealthRestore, Item->HungerRestore, Item->ThirstRestore);

	// ConsumeUse gere les deux cas : usage unique (l'exemplaire disparait) et
	// usages multiples (une charge en moins, l'objet reste tant qu'il en a).
	// On vise le slot actif precisement, et pas RemoveItem qui piocherait
	// n'importe quelle pile du meme objet, y compris dans le sac.
	InventoryComponent->ConsumeUse(InventoryComponent->GetSelectedHotbarIndex());
}

FVector AFpsPlayer::PlaceOnGround(const FVector& Start) const
{
	// APickupItem ne simule pas la physique : sans ce trace vers le bas,
	// l'objet resterait suspendu en l'air la ou on l'a lache.
	FCollisionQueryParams Params(SCENE_QUERY_STAT(DropGroundTrace), /*bTraceComplex=*/false, this);

	FHitResult GroundHit;
	if (GetWorld()->LineTraceSingleByChannel(GroundHit, Start, Start - FVector(0.f, 0.f, 500.f), ECC_Visibility, Params))
	{
		return GroundHit.ImpactPoint + FVector(0.f, 0.f, DropGroundOffset);
	}

	return Start;
}

APickupItem* AFpsPlayer::SpawnPickup(const FInventorySlot& Slot, const FVector& Location)
{
	if (!DroppedPickupClass || Slot.IsEmpty())
	{
		return nullptr;
	}

	// SpawnActorDeferred et non SpawnActor : il faut poser ItemData AVANT que
	// OnConstruction et BeginPlay tournent, sinon le pickup apparait sans mesh
	// et son CanInteract le rendrait inerte.
	const FTransform SpawnTransform(FRotator(0.f, GetActorRotation().Yaw, 0.f), Location);

	APickupItem* Dropped = GetWorld()->SpawnActorDeferred<APickupItem>(
		DroppedPickupClass, SpawnTransform, this, nullptr,
		ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

	if (!Dropped)
	{
		return nullptr;
	}

	Dropped->InitPickup(Slot.Item, Slot.Quantity, Slot.RemainingUses);
	Dropped->FinishSpawning(SpawnTransform);

	return Dropped;
}

bool AFpsPlayer::DropSlot(int32 SlotIndex, int32 Quantity)
{
	if (!DroppedPickupClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, impossible de jeter un objet."));
		return false;
	}

	FInventorySlot Taken;
	if (!InventoryComponent->TakeFromSlot(SlotIndex, Quantity, Taken) || Taken.IsEmpty())
	{
		return false;
	}

	const FVector EyeLocation = FirstPersonCamera->GetComponentLocation();
	const FVector Forward = FirstPersonCamera->GetForwardVector();
	FVector DropLocation = EyeLocation + Forward * DropDistance;

	// Un mur devant nous : on depose juste avant, sinon l'objet finirait
	// de l'autre cote de la cloison et deviendrait inatteignable.
	FCollisionQueryParams Params(SCENE_QUERY_STAT(DropItemTrace), /*bTraceComplex=*/false, this);
	FHitResult WallHit;
	if (GetWorld()->LineTraceSingleByChannel(WallHit, EyeLocation, DropLocation, ECC_Visibility, Params))
	{
		DropLocation = WallHit.ImpactPoint - Forward * 20.f;
	}

	if (!SpawnPickup(Taken, PlaceOnGround(DropLocation)))
	{
		// On ne perd pas l'objet : il retourne dans l'inventaire, usure comprise.
		InventoryComponent->AddItem(Taken.Item, Taken.Quantity, Taken.RemainingUses);
		return false;
	}

	return true;
}

void AFpsPlayer::DropAllItems()
{
	if (!DroppedPickupClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("AFpsPlayer : DroppedPickupClass n'est pas assignee, le butin est perdu."));
		return;
	}

	const int32 SlotCount = InventoryComponent->GetSlots().Num();
	const FVector Center = GetActorLocation();

	int32 Scattered = 0;
	for (int32 Index = 0; Index < SlotCount; ++Index)
	{
		FInventorySlot Taken;
		// TakeFromSlot renvoie false sur un slot vide : pas besoin de tester.
		if (!InventoryComponent->TakeFromSlot(Index, MAX_int32, Taken) || Taken.IsEmpty())
		{
			continue;
		}

		// Angle d'or : repartit les objets en spirale reguliere autour du
		// corps, sans qu'ils se superposent ni forment de motif visible.
		const float Angle = FMath::DegreesToRadians(Scattered * 137.5f);
		const float Radius = 40.f + Scattered * 4.f;
		const FVector Offset(FMath::Cos(Angle) * Radius, FMath::Sin(Angle) * Radius, 0.f);

		SpawnPickup(Taken, PlaceOnGround(Center + Offset));
		++Scattered;
	}

	UE_LOG(LogTemp, Log, TEXT("AFpsPlayer : %d piles laissees au sol a la mort."), Scattered);
}

void AFpsPlayer::HandleDeath()
{
	if (bIsDead)
	{
		return;
	}
	bIsDead = true;

	// Tout tombe sur place. On le fait AVANT de couper les inputs : l'ordre
	// n'a pas d'importance fonctionnelle, mais perdre le butin en silence
	// serait le pire bug possible dans un jeu de survie.
	DropAllItems();

	ClearTransientInputStates();
	GetCharacterMovement()->DisableMovement();

	if (APlayerController* OwningController = Cast<APlayerController>(GetController()))
	{
		DisableInput(OwningController);
	}
}

void AFpsPlayer::ClearTransientInputStates()
{
	StopSprint();
	StopJumping();

	// L'accroupissement n'est volontairement pas annule : c'est un etat
	// visible que le joueur maitrise, et il se retablit tout seul au
	// prochain appui sur la touche.
}

void AFpsPlayer::StartCrouch()
{
	// En mode bascule, la meme touche releve le personnage.
	if (bCrouchToggleMode && bIsCrouched)
	{
		StopCrouch();
		return;
	}

	StopSprint();
	Crouch();
}

void AFpsPlayer::HandleCrouchReleased()
{
	if (bCrouchToggleMode)
	{
		return;
	}

	StopCrouch();
}

void AFpsPlayer::StopCrouch()
{
	// UnCrouch() echoue silencieusement s'il y a un plafond : le perso
	// reste accroupi jusqu'a ce que la place se libere.
	UnCrouch();
}

void AFpsPlayer::Interact()
{
	InteractionComponent->TryInteract();
}

void AFpsPlayer::OnStartCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
{
	Super::OnStartCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);

	CrouchTargetAlpha = 1.f;
	CrouchTiltDirection = 1.f;
}

void AFpsPlayer::OnEndCrouch(float HalfHeightAdjust, float ScaledHalfHeightAdjust)
{
	Super::OnEndCrouch(HalfHeightAdjust, ScaledHalfHeightAdjust);

	CrouchTargetAlpha = 0.f;
	CrouchTiltDirection = -1.f;
}
